Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

wil6210: fix race between disconnect and Tx NAPI

When disconnecting some CID, corresponded Tx vring get released. During vring
release, all descriptors get freed. It is possible that Tx NAPI working on the same
vring simultaneously. If it happens, descriptor may be double freed.

To protect from the race above, make sure NAPI won't process the same vring.
Introduce 'enabled' flag in the struct vring_tx_data. Proceed with Tx NAPI only if
'enabled' flag set. Prior to Tx vring release, clear this flag and make sure NAPI
get synchronized.

NAPI enablement status protected by wil->mutex, add protection where it was
missing and check for it.

During reset, disconnect all peers first, then proceed with the Rx vring. It allows for
the disconnect flow to observe proper 'wil->status' and correctly notify cfg80211 about
connection status change

Signed-off-by: Vladimir Kondratiev <qca_vkondrat@qca.qualcomm.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

authored by

Vladimir Kondratiev and committed by
John W. Linville
097638a0 260e6951

+47 -4
+4
drivers/net/wireless/ath/wil6210/cfg80211.c
··· 674 674 struct net_device *dev, u8 *mac) 675 675 { 676 676 struct wil6210_priv *wil = wiphy_to_wil(wiphy); 677 + 678 + mutex_lock(&wil->mutex); 677 679 wil6210_disconnect(wil, mac); 680 + mutex_unlock(&wil->mutex); 681 + 678 682 return 0; 679 683 } 680 684
+13 -4
drivers/net/wireless/ath/wil6210/main.c
··· 133 133 struct wil6210_priv *wil = container_of(work, 134 134 struct wil6210_priv, disconnect_worker); 135 135 136 + mutex_lock(&wil->mutex); 136 137 _wil6210_disconnect(wil, NULL); 138 + mutex_unlock(&wil->mutex); 137 139 } 138 140 139 141 static void wil_connect_timer_fn(ulong x) ··· 262 260 { 263 261 cancel_work_sync(&wil->disconnect_worker); 264 262 cancel_work_sync(&wil->fw_error_worker); 263 + mutex_lock(&wil->mutex); 265 264 wil6210_disconnect(wil, NULL); 265 + mutex_unlock(&wil->mutex); 266 266 wmi_event_flush(wil); 267 267 destroy_workqueue(wil->wmi_wq_conn); 268 268 destroy_workqueue(wil->wmi_wq); ··· 378 374 { 379 375 int rc; 380 376 377 + WARN_ON(!mutex_is_locked(&wil->mutex)); 378 + 379 + cancel_work_sync(&wil->disconnect_worker); 380 + wil6210_disconnect(wil, NULL); 381 + 381 382 wil->status = 0; /* prevent NAPI from being scheduled */ 382 383 if (test_bit(wil_status_napi_en, &wil->status)) { 383 384 napi_synchronize(&wil->napi_rx); 384 - napi_synchronize(&wil->napi_tx); 385 385 } 386 386 387 387 if (wil->scan_request) { ··· 394 386 cfg80211_scan_done(wil->scan_request, true); 395 387 wil->scan_request = NULL; 396 388 } 397 - 398 - cancel_work_sync(&wil->disconnect_worker); 399 - wil6210_disconnect(wil, NULL); 400 389 401 390 wil6210_disable_irq(wil); 402 391 ··· 451 446 struct net_device *ndev = wil_to_ndev(wil); 452 447 struct wireless_dev *wdev = wil->wdev; 453 448 int rc; 449 + 450 + WARN_ON(!mutex_is_locked(&wil->mutex)); 454 451 455 452 rc = wil_reset(wil); 456 453 if (rc) ··· 513 506 514 507 static int __wil_down(struct wil6210_priv *wil) 515 508 { 509 + WARN_ON(!mutex_is_locked(&wil->mutex)); 510 + 516 511 clear_bit(wil_status_napi_en, &wil->status); 517 512 napi_disable(&wil->napi_rx); 518 513 napi_disable(&wil->napi_tx);
+2
drivers/net/wireless/ath/wil6210/pcie_bus.c
··· 70 70 goto stop_master; 71 71 72 72 /* need reset here to obtain MAC */ 73 + mutex_lock(&wil->mutex); 73 74 rc = wil_reset(wil); 75 + mutex_unlock(&wil->mutex); 74 76 if (rc) 75 77 goto release_irq; 76 78
+17
drivers/net/wireless/ath/wil6210/txrx.c
··· 618 618 struct wmi_vring_cfg_done_event cmd; 619 619 } __packed reply; 620 620 struct vring *vring = &wil->vring_tx[id]; 621 + struct vring_tx_data *txdata = &wil->vring_tx_data[id]; 621 622 622 623 if (vring->va) { 623 624 wil_err(wil, "Tx ring [%d] already allocated\n", id); ··· 626 625 goto out; 627 626 } 628 627 628 + memset(txdata, 0, sizeof(*txdata)); 629 629 vring->size = size; 630 630 rc = wil_vring_alloc(wil, vring); 631 631 if (rc) ··· 650 648 } 651 649 vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); 652 650 651 + txdata->enabled = 1; 652 + 653 653 return 0; 654 654 out_free: 655 655 wil_vring_free(wil, vring, 1); ··· 664 660 { 665 661 struct vring *vring = &wil->vring_tx[id]; 666 662 663 + WARN_ON(!mutex_is_locked(&wil->mutex)); 664 + 667 665 if (!vring->va) 668 666 return; 667 + 668 + /* make sure NAPI won't touch this vring */ 669 + wil->vring_tx_data[id].enabled = 0; 670 + if (test_bit(wil_status_napi_en, &wil->status)) 671 + napi_synchronize(&wil->napi_tx); 669 672 670 673 wil_vring_free(wil, vring, 1); 671 674 } ··· 1039 1028 struct net_device *ndev = wil_to_ndev(wil); 1040 1029 struct device *dev = wil_to_dev(wil); 1041 1030 struct vring *vring = &wil->vring_tx[ringid]; 1031 + struct vring_tx_data *txdata = &wil->vring_tx_data[ringid]; 1042 1032 int done = 0; 1043 1033 int cid = wil->vring2cid_tid[ringid][0]; 1044 1034 struct wil_net_stats *stats = &wil->sta[cid].stats; ··· 1047 1035 1048 1036 if (!vring->va) { 1049 1037 wil_err(wil, "Tx irq[%d]: vring not initialized\n", ringid); 1038 + return 0; 1039 + } 1040 + 1041 + if (!txdata->enabled) { 1042 + wil_info(wil, "Tx irq[%d]: vring disabled\n", ringid); 1050 1043 return 0; 1051 1044 } 1052 1045
+9
drivers/net/wireless/ath/wil6210/wil6210.h
··· 243 243 struct wil_ctx *ctx; /* ctx[size] - software context */ 244 244 }; 245 245 246 + /** 247 + * Additional data for Tx Vring 248 + */ 249 + struct vring_tx_data { 250 + int enabled; 251 + 252 + }; 253 + 246 254 enum { /* for wil6210_priv.status */ 247 255 wil_status_fwready = 0, 248 256 wil_status_fwconnecting, ··· 394 386 /* DMA related */ 395 387 struct vring vring_rx; 396 388 struct vring vring_tx[WIL6210_MAX_TX_RINGS]; 389 + struct vring_tx_data vring_tx_data[WIL6210_MAX_TX_RINGS]; 397 390 u8 vring2cid_tid[WIL6210_MAX_TX_RINGS][2]; /* [0] - CID, [1] - TID */ 398 391 struct wil_sta_info sta[WIL6210_MAX_CID]; 399 392 /* scan */
+2
drivers/net/wireless/ath/wil6210/wmi.c
··· 462 462 463 463 wil->sinfo_gen++; 464 464 465 + mutex_lock(&wil->mutex); 465 466 wil6210_disconnect(wil, evt->bssid); 467 + mutex_unlock(&wil->mutex); 466 468 } 467 469 468 470 static void wmi_evt_notify(struct wil6210_priv *wil, int id, void *d, int len)