1: 55b369fc3f99 ! 1: 38a8e1d9d0d1 wifi: ath11k: release peer accounting on peer delete timeout @@ Commit message 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. + 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.  - 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. + 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.  - 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. + 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 @@ drivers/net/wireless/ath/ath11k/peer.c: static int __ath11k_peer_delete(struct a  - 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. ++ /* ++ * 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);