• Wen Gong's avatar
    wifi: ath11k: avoid deadlock during regulatory update in ath11k_regd_update() · d99884ad
    Wen Gong authored
    Running this test in a loop it is easy to reproduce an rtnl deadlock:
    
    iw reg set FI
    ifconfig wlan0 down
    
    What happens is that thread A (workqueue) tries to update the regulatory:
    
        try to acquire the rtnl_lock of ar->regd_update_work
    
        rtnl_lock+0x17/0x20
        ath11k_regd_update+0x15a/0x260 [ath11k]
        ath11k_regd_update_work+0x15/0x20 [ath11k]
        process_one_work+0x228/0x670
        worker_thread+0x4d/0x440
        kthread+0x16d/0x1b0
        ret_from_fork+0x22/0x30
    
    And thread B (ifconfig) tries to stop the interface:
    
        try to cancel_work_sync(&ar->regd_update_work) in ath11k_mac_op_stop().
        ifconfig  3109 [003]  2414.232506: probe:
    
        ath11k_mac_op_stop: (ffffffffc14187a0)
        drv_stop+0x30 ([mac80211])
        ieee80211_do_stop+0x5d2 ([mac80211])
        ieee80211_stop+0x3e ([mac80211])
        __dev_close_many+0x9e ([kernel.kallsyms])
        __dev_change_flags+0xbe ([kernel.kallsyms])
        dev_change_flags+0x23 ([kernel.kallsyms])
        devinet_ioctl+0x5e3 ([kernel.kallsyms])
        inet_ioctl+0x197 ([kernel.kallsyms])
        sock_do_ioctl+0x4d ([kernel.kallsyms])
        sock_ioctl+0x264 ([kernel.kallsyms])
        __x64_sys_ioctl+0x92 ([kernel.kallsyms])
        do_syscall_64+0x3a ([kernel.kallsyms])
        entry_SYSCALL_64_after_hwframe+0x63 ([kernel.kallsyms])
        __GI___ioctl+0x7 (/lib/x86_64-linux-gnu/libc-2.23.so)
    
    The sequence of deadlock is:
    
    1. Thread B calls rtnl_lock().
    
    2. Thread A starts to run and calls rtnl_lock() from within
       ath11k_regd_update_work(), then enters wait state because the lock is owned by
       thread B.
    
    3. Thread B continues to run and tries to call
       cancel_work_sync(&ar->regd_update_work), but thread A is in
       ath11k_regd_update_work() waiting for rtnl_lock(). So cancel_work_sync()
       forever waits for ath11k_regd_update_work() to finish and we have a deadlock.
    
    Fix this by switching from using regulatory_set_wiphy_regd_sync() to
    regulatory_set_wiphy_regd(). Now cfg80211 will schedule another workqueue which
    handles the locking on it's own. So the ath11k workqueue can simply exit without
    taking any locks, avoiding the deadlock.
    
    Tested-on: WCN6855 hw2.0 PCI WLAN.HSP.1.1-03125-QCAHSPSWPL_V1_V2_SILICONZ_LITE-3
    Signed-off-by: default avatarWen Gong <quic_wgong@quicinc.com>
    [kvalo: improve commit log]
    Signed-off-by: default avatarKalle Valo <quic_kvalo@quicinc.com>
    Link: https://lore.kernel.org/r/20221006151747.13757-1-kvalo@kernel.org
    d99884ad
reg.c 20.3 KB