[PATCH] wifi: ath11k: release peer accounting on peer delete timeout

Michael Pfeifroth posted 1 patch 2 days, 1 hour ago
There is a newer version of this series
drivers/net/wireless/ath/ath11k/peer.c | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
[PATCH] wifi: ath11k: release peer accounting on peer delete timeout
Posted by Michael Pfeifroth 2 days, 1 hour ago
On some deployments access points intermittently stop accepting new
station associations after several hours of uptime with frequent
roaming/reconnects. The kernel logs

  ath11k_pci ....: failed to create peer due to insufficient peer entry resource in firmware

and hostapd reports "Could not add STA to kernel driver". A "wifi
down/up" (radio restart) on the affected radio restores service.

Despite the message text, this is not a firmware peer-table exhaustion.
The message is emitted by the driver-side gate in ath11k_peer_create():

	if (ar->num_peers > (ar->max_num_peers - 1))

i.e. the driver's own ar->num_peers accounting has leaked and reached
the ceiling. It is always preceded by a peer-delete that timed out:

  ath11k_pci ....: invalid vdev id in peer delete resp ev 1
  ath11k_pci ....: Timeout in receiving peer delete response
  ath11k_pci ....: failed to delete peer vdev_id .. addr .. ret -110

On such a timeout __ath11k_peer_delete() returns early without removing
the local peer object, and ath11k_peer_delete() consequently skips the
ar->num_peers-- decrement (it only runs on the success path). The normal
free happens asynchronously in ath11k_peer_unmap_event(), which never
runs when the delete response is lost or misrouted (e.g. because the
vdev is already gone by the time the response is processed, hence the
"invalid vdev id in peer delete resp ev" warning). Each timed-out delete
therefore leaks one ar->num_peers slot until max_num_peers is reached
and all further ath11k_peer_create() calls fail.

Free the local peer on the timeout path and return success so that
ath11k_peer_delete() releases the num_peers slot. The peer has already
been removed from the rhash earlier in __ath11k_peer_delete(), so only
the list removal and free remain, mirroring ath11k_peer_unmap_event().
A late unmap event will then simply fail to find the peer id and log a
harmless warning instead of touching freed memory.

The problem was reproduced deterministically with a fault-injection
patch that forces the peer-delete wait to time out: after max_num_peers
such deletes the AP permanently rejects new stations, and with this
change it keeps accepting them.

Fixes: 690ace20ff79 ("ath11k: peer delete synchronization with firmware")
Cc: stable@vger.kernel.org
Signed-off-by: Michael Pfeifroth <micpf@westermo.com>
---
 drivers/net/wireless/ath/ath11k/peer.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/ath/ath11k/peer.c b/drivers/net/wireless/ath/ath11k/peer.c
index b30a906..ed2d7d8 100644
--- a/drivers/net/wireless/ath/ath11k/peer.c
+++ b/drivers/net/wireless/ath/ath11k/peer.c
@@ -341,8 +341,20 @@ static int __ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, const u8 *addr)
 	}
 
 	ret = ath11k_wait_for_peer_delete_done(ar, vdev_id, addr);
-	if (ret)
-		return ret;
+	if (ret) {
+		/* The firmware delete confirmation was lost; free the local
+		 * peer here (already removed from the rhash above) so that
+		 * ath11k_peer_delete() releases the ar->num_peers slot instead
+		 * of leaking it.
+		 */
+		spin_lock_bh(&ab->base_lock);
+		peer = ath11k_peer_find(ab, vdev_id, addr);
+		if (peer) {
+			list_del(&peer->list);
+			kfree(peer);
+		}
+		spin_unlock_bh(&ab->base_lock);
+	}
 
 	return 0;
 }
-- 
2.34.1
Re: [PATCH] wifi: ath11k: release peer accounting on peer delete timeout
Posted by Baochen Qiang 6 hours ago

On 9/22/2026 8:25 PM, Michael Pfeifroth wrote:
> On some deployments access points intermittently stop accepting new
> station associations after several hours of uptime with frequent
> roaming/reconnects. The kernel logs
> 
>   ath11k_pci ....: failed to create peer due to insufficient peer entry resource in firmware
> 
> and hostapd reports "Could not add STA to kernel driver". A "wifi
> down/up" (radio restart) on the affected radio restores service.
> 
> Despite the message text, this is not a firmware peer-table exhaustion.
> The message is emitted by the driver-side gate in ath11k_peer_create():
> 
> 	if (ar->num_peers > (ar->max_num_peers - 1))
> 
> i.e. the driver's own ar->num_peers accounting has leaked and reached
> the ceiling. It is always preceded by a peer-delete that timed out:
> 
>   ath11k_pci ....: invalid vdev id in peer delete resp ev 1
>   ath11k_pci ....: Timeout in receiving peer delete response
>   ath11k_pci ....: failed to delete peer vdev_id .. addr .. ret -110
> 
> On such a timeout __ath11k_peer_delete() returns early without removing
> the local peer object, and ath11k_peer_delete() consequently skips the
> ar->num_peers-- decrement (it only runs on the success path). The normal
> free happens asynchronously in ath11k_peer_unmap_event(), which never

Hmm, I don't think so. host waits for peer unmap event in ath11k_wait_for_peer_deleted(),
before waiting for peer delete response. Since there is no "failed wait for peer deleted"
log, unmap event is good and ath11k_peer_unmap_event() runs.

> runs when the delete response is lost or misrouted (e.g. because the

what does 'misrouted' mean?

> vdev is already gone by the time the response is processed, hence the

I have never seen it, but yeas it can happen theoretically.

> "invalid vdev id in peer delete resp ev" warning). Each timed-out delete
> therefore leaks one ar->num_peers slot until max_num_peers is reached
> and all further ath11k_peer_create() calls fail.
> 
> Free the local peer on the timeout path and return success so that
> ath11k_peer_delete() releases the num_peers slot. The peer has already
> been removed from the rhash earlier in __ath11k_peer_delete(), so only
> the list removal and free remain, mirroring ath11k_peer_unmap_event().
> A late unmap event will then simply fail to find the peer id and log a
> harmless warning instead of touching freed memory.

As stated above peer unmap is good hence peer is already freed there. The code here frees
peer only when it indeed not freed.

> 
> The problem was reproduced deterministically with a fault-injection

curious what the patch does? does it modify ath11k codebase?

> patch that forces the peer-delete wait to time out: after max_num_peers
> such deletes the AP permanently rejects new stations, and with this
> change it keeps accepting them.
> 
> Fixes: 690ace20ff79 ("ath11k: peer delete synchronization with firmware")
> Cc: stable@vger.kernel.org
> Signed-off-by: Michael Pfeifroth <micpf@westermo.com>
> ---
>  drivers/net/wireless/ath/ath11k/peer.c | 16 ++++++++++++++--
>  1 file changed, 14 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/net/wireless/ath/ath11k/peer.c b/drivers/net/wireless/ath/ath11k/peer.c
> index b30a906..ed2d7d8 100644
> --- a/drivers/net/wireless/ath/ath11k/peer.c
> +++ b/drivers/net/wireless/ath/ath11k/peer.c
> @@ -341,8 +341,20 @@ static int __ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, const u8 *addr)
>  	}
>  
>  	ret = ath11k_wait_for_peer_delete_done(ar, vdev_id, addr);
> -	if (ret)
> -		return ret;
> +	if (ret) {
> +		/* The firmware delete confirmation was lost; free the local

ath11k now follows networking subsystem comment style and it prefers a '/*' by itself on
the first line.

> +		 * peer here (already removed from the rhash above) so that
> +		 * ath11k_peer_delete() releases the ar->num_peers slot instead
> +		 * of leaking it.

the comment needs rephrase to reflect that peer delete needed only when it not freed yet.
> +		 */
> +		spin_lock_bh(&ab->base_lock);
> +		peer = ath11k_peer_find(ab, vdev_id, addr);
> +		if (peer) {
> +			list_del(&peer->list);
> +			kfree(peer);
> +		}
> +		spin_unlock_bh(&ab->base_lock);
> +	}
>  
>  	return 0;
>  }
Re: [PATCH] wifi: ath11k: release peer accounting on peer delete timeout
Posted by Michael Pfeifroth 5 hours ago
On 9/24/2026 9:29 AM, Baochen Qiang wrote:
>> On such a timeout __ath11k_peer_delete() returns early without removing
>> the local peer object, and ath11k_peer_delete() consequently skips the
>> ar->num_peers-- decrement (it only runs on the success path). The normal
>> free happens asynchronously in ath11k_peer_unmap_event(), which never
> Hmm, I don't think so. host waits for peer unmap event in ath11k_wait_for_peer_deleted(),
> before waiting for peer delete response. Since there is no "failed wait for peer deleted"
> log, unmap event is good and ath11k_peer_unmap_event() runs.

You are right, thanks. The unmap event is received, so the peer is
already removed from ab->peers and freed; what actually leaks is only the
ar->num_peers counter, because __ath11k_peer_delete() returns -ETIMEDOUT
from the *second* wait (the delete-response completion) and
ath11k_peer_delete() therefore skips the num_peers-- decrement.

So the essential fix is to return success on the timeout path. The
list_del()/kfree() is only a safety net for the other timeout case, where
ath11k_wait_for_peer_deleted() itself times out and no unmap event ever
removed the peer; that is why it is guarded by "if (peer)". I have
reworded the commit message and the code comment accordingly in v2.

>> runs when the delete response is lost or misrouted (e.g. because the
> what does 'misrouted' mean?

Poor wording on my side, dropped in v2. What I meant is the case you
describe below: the delete-response event is dropped in
ath11k_peer_delete_resp_event() because ath11k_mac_get_ar_by_vdev_id()
cannot resolve the vdev id (the "invalid vdev id in peer delete resp ev"
warning), so complete(&ar->peer_delete_done) is never called and the
wait times out even though firmware did delete the peer.

>> The problem was reproduced deterministically with a fault-injection
> curious what the patch does? does it modify ath11k codebase?

Yes, it is an out-of-tree debug-only patch (not part of this submission).
It adds a few module parameters to ath11k: one forces the next N
ath11k_wait_for_peer_delete_done() calls to return -ETIMEDOUT, and one
caps max_num_peers so the ceiling is hit after only a few deletes. With
the counter leak in place the AP stops accepting new stations after
max_num_peers forced timeouts; with this fix it keeps accepting them.

I have addressed the comment style (/* on its own first line) in v2 as
well. v2 follows shortly.

Thanks,
Michael
[PATCH v2] wifi: ath11k: release peer accounting on peer delete timeout
Posted by Michael Pfeifroth 5 hours ago
On some deployments access points intermittently stop accepting new
station associations after several hours of uptime with frequent
roaming/reconnects. The kernel logs

  ath11k_pci ....: failed to create peer due to insufficient peer entry resource in firmware

and hostapd reports "Could not add STA to kernel driver". A "wifi
down/up" (radio restart) on the affected radio restores service.

Despite the message text, this is not a firmware peer-table exhaustion.
The message is emitted by the driver-side gate in ath11k_peer_create():

	if (ar->num_peers > (ar->max_num_peers - 1))

i.e. the driver's own ar->num_peers accounting has leaked and reached
the ceiling. It is always preceded by a peer-delete that timed out:

  ath11k_pci ....: invalid vdev id in peer delete resp ev 1
  ath11k_pci ....: Timeout in receiving peer delete response
  ath11k_pci ....: failed to delete peer vdev_id .. addr .. ret -110

ath11k_peer_delete() only decrements ar->num_peers when
__ath11k_peer_delete() returns 0. On a delete timeout
__ath11k_peer_delete() returns -ETIMEDOUT, so the decrement is skipped
and one num_peers slot is leaked per event. After max_num_peers such
timeouts ath11k_peer_create() rejects every new station until the radio
is restarted.

In the observed case the peer-unmap event is received normally (there is
no "failed wait for peer deleted" log), so ath11k_peer_unmap_event() has
already removed the peer from ab->peers and freed it; only the num_peers
counter is left wrong. The delete-response completion is missed because
the response event is dropped in ath11k_peer_delete_resp_event() when
ath11k_mac_get_ar_by_vdev_id() cannot resolve the vdev ("invalid vdev id
in peer delete resp ev"), so ar->peer_delete_done is never signalled and
the second wait in ath11k_wait_for_peer_delete_done() times out.

Return success from __ath11k_peer_delete() on the timeout path so that
ath11k_peer_delete() releases the num_peers slot. As a safety net also
drop the local peer if it is still on the list; that only happens in the
other timeout case, where ath11k_wait_for_peer_deleted() itself timed out
and no unmap event removed the peer. The peer has already been removed
from the rhash earlier in __ath11k_peer_delete(), so only the list
removal and free remain, mirroring ath11k_peer_unmap_event(). A late
unmap event would then simply fail to find the peer id and log a harmless
warning instead of touching freed memory.

The problem was reproduced deterministically with an out-of-tree debug
patch that adds module parameters to force
ath11k_wait_for_peer_delete_done() to return -ETIMEDOUT and to cap
max_num_peers: after max_num_peers such deletes the AP permanently
rejects new stations, and with this change it keeps accepting them.

Fixes: 690ace20ff79 ("ath11k: peer delete synchronization with firmware")
Cc: stable@vger.kernel.org
Signed-off-by: Michael Pfeifroth <micpf@westermo.com>
---
v2:
 - Correct the root-cause description: the peer-unmap event is received
   normally, so the peer is already freed; only the num_peers counter
   leaks (Baochen Qiang).
 - Explain that the missed delete-response completion is due to the event
   being dropped on an unresolved vdev id, replacing the vague
   "misrouted" wording.
 - Rework the code comment and switch to netdev comment style.
 drivers/net/wireless/ath/ath11k/peer.c | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/ath/ath11k/peer.c b/drivers/net/wireless/ath/ath11k/peer.c
index b30a906..f93a8da 100644
--- a/drivers/net/wireless/ath/ath11k/peer.c
+++ b/drivers/net/wireless/ath/ath11k/peer.c
@@ -341,8 +341,21 @@ static int __ath11k_peer_delete(struct ath11k *ar, u32 vdev_id, const u8 *addr)
 	}
 
 	ret = ath11k_wait_for_peer_delete_done(ar, vdev_id, addr);
-	if (ret)
-		return ret;
+	if (ret) {
+		/*
+		 * The delete timed out. The peer is normally already freed by
+		 * the unmap event; drop it here only if it is still on the
+		 * list. Either way return success so that ath11k_peer_delete()
+		 * releases the num_peers slot instead of leaking it.
+		 */
+		spin_lock_bh(&ab->base_lock);
+		peer = ath11k_peer_find(ab, vdev_id, addr);
+		if (peer) {
+			list_del(&peer->list);
+			kfree(peer);
+		}
+		spin_unlock_bh(&ab->base_lock);
+	}
 
 	return 0;
 }
-- 
2.34.1