diff options
author | Vladimir Kondratiev <qca_vkondrat@qca.qualcomm.com> | 2014-03-17 09:34:25 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2014-03-17 13:44:19 -0400 |
commit | 097638a08acde0320c44969a5dff3af105c341a0 (patch) | |
tree | 44551edf737386ad546046171d0ac2dc3b972283 /drivers | |
parent | 260e695196de8b91bbab482d3804e4e0312a59b6 (diff) |
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>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/wireless/ath/wil6210/cfg80211.c | 4 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wil6210/main.c | 17 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wil6210/pcie_bus.c | 2 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wil6210/txrx.c | 17 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wil6210/wil6210.h | 9 | ||||
-rw-r--r-- | drivers/net/wireless/ath/wil6210/wmi.c | 2 |
6 files changed, 47 insertions, 4 deletions
diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index ed5a7e145027..4806a49cb61b 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c | |||
@@ -674,7 +674,11 @@ static int wil_cfg80211_del_station(struct wiphy *wiphy, | |||
674 | struct net_device *dev, u8 *mac) | 674 | struct net_device *dev, u8 *mac) |
675 | { | 675 | { |
676 | struct wil6210_priv *wil = wiphy_to_wil(wiphy); | 676 | struct wil6210_priv *wil = wiphy_to_wil(wiphy); |
677 | |||
678 | mutex_lock(&wil->mutex); | ||
677 | wil6210_disconnect(wil, mac); | 679 | wil6210_disconnect(wil, mac); |
680 | mutex_unlock(&wil->mutex); | ||
681 | |||
678 | return 0; | 682 | return 0; |
679 | } | 683 | } |
680 | 684 | ||
diff --git a/drivers/net/wireless/ath/wil6210/main.c b/drivers/net/wireless/ath/wil6210/main.c index 0005d9b90772..95f4efe9ef37 100644 --- a/drivers/net/wireless/ath/wil6210/main.c +++ b/drivers/net/wireless/ath/wil6210/main.c | |||
@@ -133,7 +133,9 @@ static void wil_disconnect_worker(struct work_struct *work) | |||
133 | struct wil6210_priv *wil = container_of(work, | 133 | struct wil6210_priv *wil = container_of(work, |
134 | struct wil6210_priv, disconnect_worker); | 134 | struct wil6210_priv, disconnect_worker); |
135 | 135 | ||
136 | mutex_lock(&wil->mutex); | ||
136 | _wil6210_disconnect(wil, NULL); | 137 | _wil6210_disconnect(wil, NULL); |
138 | mutex_unlock(&wil->mutex); | ||
137 | } | 139 | } |
138 | 140 | ||
139 | static void wil_connect_timer_fn(ulong x) | 141 | static void wil_connect_timer_fn(ulong x) |
@@ -260,7 +262,9 @@ void wil_priv_deinit(struct wil6210_priv *wil) | |||
260 | { | 262 | { |
261 | cancel_work_sync(&wil->disconnect_worker); | 263 | cancel_work_sync(&wil->disconnect_worker); |
262 | cancel_work_sync(&wil->fw_error_worker); | 264 | cancel_work_sync(&wil->fw_error_worker); |
265 | mutex_lock(&wil->mutex); | ||
263 | wil6210_disconnect(wil, NULL); | 266 | wil6210_disconnect(wil, NULL); |
267 | mutex_unlock(&wil->mutex); | ||
264 | wmi_event_flush(wil); | 268 | wmi_event_flush(wil); |
265 | destroy_workqueue(wil->wmi_wq_conn); | 269 | destroy_workqueue(wil->wmi_wq_conn); |
266 | destroy_workqueue(wil->wmi_wq); | 270 | destroy_workqueue(wil->wmi_wq); |
@@ -374,10 +378,14 @@ int wil_reset(struct wil6210_priv *wil) | |||
374 | { | 378 | { |
375 | int rc; | 379 | int rc; |
376 | 380 | ||
381 | WARN_ON(!mutex_is_locked(&wil->mutex)); | ||
382 | |||
383 | cancel_work_sync(&wil->disconnect_worker); | ||
384 | wil6210_disconnect(wil, NULL); | ||
385 | |||
377 | wil->status = 0; /* prevent NAPI from being scheduled */ | 386 | wil->status = 0; /* prevent NAPI from being scheduled */ |
378 | if (test_bit(wil_status_napi_en, &wil->status)) { | 387 | if (test_bit(wil_status_napi_en, &wil->status)) { |
379 | napi_synchronize(&wil->napi_rx); | 388 | napi_synchronize(&wil->napi_rx); |
380 | napi_synchronize(&wil->napi_tx); | ||
381 | } | 389 | } |
382 | 390 | ||
383 | if (wil->scan_request) { | 391 | if (wil->scan_request) { |
@@ -387,9 +395,6 @@ int wil_reset(struct wil6210_priv *wil) | |||
387 | wil->scan_request = NULL; | 395 | wil->scan_request = NULL; |
388 | } | 396 | } |
389 | 397 | ||
390 | cancel_work_sync(&wil->disconnect_worker); | ||
391 | wil6210_disconnect(wil, NULL); | ||
392 | |||
393 | wil6210_disable_irq(wil); | 398 | wil6210_disable_irq(wil); |
394 | 399 | ||
395 | wmi_event_flush(wil); | 400 | wmi_event_flush(wil); |
@@ -447,6 +452,8 @@ static int __wil_up(struct wil6210_priv *wil) | |||
447 | struct wireless_dev *wdev = wil->wdev; | 452 | struct wireless_dev *wdev = wil->wdev; |
448 | int rc; | 453 | int rc; |
449 | 454 | ||
455 | WARN_ON(!mutex_is_locked(&wil->mutex)); | ||
456 | |||
450 | rc = wil_reset(wil); | 457 | rc = wil_reset(wil); |
451 | if (rc) | 458 | if (rc) |
452 | return rc; | 459 | return rc; |
@@ -506,6 +513,8 @@ int wil_up(struct wil6210_priv *wil) | |||
506 | 513 | ||
507 | static int __wil_down(struct wil6210_priv *wil) | 514 | static int __wil_down(struct wil6210_priv *wil) |
508 | { | 515 | { |
516 | WARN_ON(!mutex_is_locked(&wil->mutex)); | ||
517 | |||
509 | clear_bit(wil_status_napi_en, &wil->status); | 518 | clear_bit(wil_status_napi_en, &wil->status); |
510 | napi_disable(&wil->napi_rx); | 519 | napi_disable(&wil->napi_rx); |
511 | napi_disable(&wil->napi_tx); | 520 | napi_disable(&wil->napi_tx); |
diff --git a/drivers/net/wireless/ath/wil6210/pcie_bus.c b/drivers/net/wireless/ath/wil6210/pcie_bus.c index c60976144db3..58fc0962e2e2 100644 --- a/drivers/net/wireless/ath/wil6210/pcie_bus.c +++ b/drivers/net/wireless/ath/wil6210/pcie_bus.c | |||
@@ -70,7 +70,9 @@ static int wil_if_pcie_enable(struct wil6210_priv *wil) | |||
70 | goto stop_master; | 70 | goto stop_master; |
71 | 71 | ||
72 | /* need reset here to obtain MAC */ | 72 | /* need reset here to obtain MAC */ |
73 | mutex_lock(&wil->mutex); | ||
73 | rc = wil_reset(wil); | 74 | rc = wil_reset(wil); |
75 | mutex_unlock(&wil->mutex); | ||
74 | if (rc) | 76 | if (rc) |
75 | goto release_irq; | 77 | goto release_irq; |
76 | 78 | ||
diff --git a/drivers/net/wireless/ath/wil6210/txrx.c b/drivers/net/wireless/ath/wil6210/txrx.c index cfd36cc0336b..c8c547457eb4 100644 --- a/drivers/net/wireless/ath/wil6210/txrx.c +++ b/drivers/net/wireless/ath/wil6210/txrx.c | |||
@@ -618,6 +618,7 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, | |||
618 | struct wmi_vring_cfg_done_event cmd; | 618 | struct wmi_vring_cfg_done_event cmd; |
619 | } __packed reply; | 619 | } __packed reply; |
620 | struct vring *vring = &wil->vring_tx[id]; | 620 | struct vring *vring = &wil->vring_tx[id]; |
621 | struct vring_tx_data *txdata = &wil->vring_tx_data[id]; | ||
621 | 622 | ||
622 | if (vring->va) { | 623 | if (vring->va) { |
623 | wil_err(wil, "Tx ring [%d] already allocated\n", id); | 624 | wil_err(wil, "Tx ring [%d] already allocated\n", id); |
@@ -625,6 +626,7 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, | |||
625 | goto out; | 626 | goto out; |
626 | } | 627 | } |
627 | 628 | ||
629 | memset(txdata, 0, sizeof(*txdata)); | ||
628 | vring->size = size; | 630 | vring->size = size; |
629 | rc = wil_vring_alloc(wil, vring); | 631 | rc = wil_vring_alloc(wil, vring); |
630 | if (rc) | 632 | if (rc) |
@@ -648,6 +650,8 @@ int wil_vring_init_tx(struct wil6210_priv *wil, int id, int size, | |||
648 | } | 650 | } |
649 | vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); | 651 | vring->hwtail = le32_to_cpu(reply.cmd.tx_vring_tail_ptr); |
650 | 652 | ||
653 | txdata->enabled = 1; | ||
654 | |||
651 | return 0; | 655 | return 0; |
652 | out_free: | 656 | out_free: |
653 | wil_vring_free(wil, vring, 1); | 657 | wil_vring_free(wil, vring, 1); |
@@ -660,9 +664,16 @@ void wil_vring_fini_tx(struct wil6210_priv *wil, int id) | |||
660 | { | 664 | { |
661 | struct vring *vring = &wil->vring_tx[id]; | 665 | struct vring *vring = &wil->vring_tx[id]; |
662 | 666 | ||
667 | WARN_ON(!mutex_is_locked(&wil->mutex)); | ||
668 | |||
663 | if (!vring->va) | 669 | if (!vring->va) |
664 | return; | 670 | return; |
665 | 671 | ||
672 | /* make sure NAPI won't touch this vring */ | ||
673 | wil->vring_tx_data[id].enabled = 0; | ||
674 | if (test_bit(wil_status_napi_en, &wil->status)) | ||
675 | napi_synchronize(&wil->napi_tx); | ||
676 | |||
666 | wil_vring_free(wil, vring, 1); | 677 | wil_vring_free(wil, vring, 1); |
667 | } | 678 | } |
668 | 679 | ||
@@ -1028,6 +1039,7 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid) | |||
1028 | struct net_device *ndev = wil_to_ndev(wil); | 1039 | struct net_device *ndev = wil_to_ndev(wil); |
1029 | struct device *dev = wil_to_dev(wil); | 1040 | struct device *dev = wil_to_dev(wil); |
1030 | struct vring *vring = &wil->vring_tx[ringid]; | 1041 | struct vring *vring = &wil->vring_tx[ringid]; |
1042 | struct vring_tx_data *txdata = &wil->vring_tx_data[ringid]; | ||
1031 | int done = 0; | 1043 | int done = 0; |
1032 | int cid = wil->vring2cid_tid[ringid][0]; | 1044 | int cid = wil->vring2cid_tid[ringid][0]; |
1033 | struct wil_net_stats *stats = &wil->sta[cid].stats; | 1045 | struct wil_net_stats *stats = &wil->sta[cid].stats; |
@@ -1038,6 +1050,11 @@ int wil_tx_complete(struct wil6210_priv *wil, int ringid) | |||
1038 | return 0; | 1050 | return 0; |
1039 | } | 1051 | } |
1040 | 1052 | ||
1053 | if (!txdata->enabled) { | ||
1054 | wil_info(wil, "Tx irq[%d]: vring disabled\n", ringid); | ||
1055 | return 0; | ||
1056 | } | ||
1057 | |||
1041 | wil_dbg_txrx(wil, "%s(%d)\n", __func__, ringid); | 1058 | wil_dbg_txrx(wil, "%s(%d)\n", __func__, ringid); |
1042 | 1059 | ||
1043 | while (!wil_vring_is_empty(vring)) { | 1060 | while (!wil_vring_is_empty(vring)) { |
diff --git a/drivers/net/wireless/ath/wil6210/wil6210.h b/drivers/net/wireless/ath/wil6210/wil6210.h index fb1006b2a4e2..2a2dec75f026 100644 --- a/drivers/net/wireless/ath/wil6210/wil6210.h +++ b/drivers/net/wireless/ath/wil6210/wil6210.h | |||
@@ -243,6 +243,14 @@ struct vring { | |||
243 | struct wil_ctx *ctx; /* ctx[size] - software context */ | 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 | enum { /* for wil6210_priv.status */ | 254 | enum { /* for wil6210_priv.status */ |
247 | wil_status_fwready = 0, | 255 | wil_status_fwready = 0, |
248 | wil_status_fwconnecting, | 256 | wil_status_fwconnecting, |
@@ -386,6 +394,7 @@ struct wil6210_priv { | |||
386 | /* DMA related */ | 394 | /* DMA related */ |
387 | struct vring vring_rx; | 395 | struct vring vring_rx; |
388 | struct vring vring_tx[WIL6210_MAX_TX_RINGS]; | 396 | struct vring vring_tx[WIL6210_MAX_TX_RINGS]; |
397 | struct vring_tx_data vring_tx_data[WIL6210_MAX_TX_RINGS]; | ||
389 | u8 vring2cid_tid[WIL6210_MAX_TX_RINGS][2]; /* [0] - CID, [1] - TID */ | 398 | u8 vring2cid_tid[WIL6210_MAX_TX_RINGS][2]; /* [0] - CID, [1] - TID */ |
390 | struct wil_sta_info sta[WIL6210_MAX_CID]; | 399 | struct wil_sta_info sta[WIL6210_MAX_CID]; |
391 | /* scan */ | 400 | /* scan */ |
diff --git a/drivers/net/wireless/ath/wil6210/wmi.c b/drivers/net/wireless/ath/wil6210/wmi.c index 58c3afcf839d..2ba56eef0c45 100644 --- a/drivers/net/wireless/ath/wil6210/wmi.c +++ b/drivers/net/wireless/ath/wil6210/wmi.c | |||
@@ -462,7 +462,9 @@ static void wmi_evt_disconnect(struct wil6210_priv *wil, int id, | |||
462 | 462 | ||
463 | wil->sinfo_gen++; | 463 | wil->sinfo_gen++; |
464 | 464 | ||
465 | mutex_lock(&wil->mutex); | ||
465 | wil6210_disconnect(wil, evt->bssid); | 466 | wil6210_disconnect(wil, evt->bssid); |
467 | mutex_unlock(&wil->mutex); | ||
466 | } | 468 | } |
467 | 469 | ||
468 | static void wmi_evt_notify(struct wil6210_priv *wil, int id, void *d, int len) | 470 | static void wmi_evt_notify(struct wil6210_priv *wil, int id, void *d, int len) |