aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBob Copeland <me@bobcopeland.com>2015-10-24 13:42:15 -0400
committerKalle Valo <kvalo@codeaurora.org>2015-10-28 14:58:06 -0400
commit8e8e54c490032f15779d7b199548eb0143b70f0f (patch)
tree01fa15ec47331fbd1dbe6ed306d2e7998d3555ad
parent56bae464275ac57cbf993f3ed15e96d6e1ec00a2 (diff)
wcn36xx: introduce per-channel ring buffer locks
wcn36xx implements a ring buffer for transmitted frames for each (high and low priority) DMA channel. The ring buffers are lockless: new frames are inserted at the head of the queue, while finished packets are reaped from the tail. Unfortunately, the list manipulations are missing any kind of barriers so are susceptible to various races: for example, a TX completion handler might read an updated desc->ctrl before the head has actually advanced, and then null out the ctl->skb pointer while it is still being used in the TX path. Simplify things here by adding a spin lock when traversing the ring. This change increased stability for me without adding any noticeable overhead on my platform (xperia z). Signed-off-by: Bob Copeland <me@bobcopeland.com> Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
-rw-r--r--drivers/net/wireless/ath/wcn36xx/dxe.c27
-rw-r--r--drivers/net/wireless/ath/wcn36xx/dxe.h1
2 files changed, 20 insertions, 8 deletions
diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.c b/drivers/net/wireless/ath/wcn36xx/dxe.c
index 086549b732b9..26085f72fc6a 100644
--- a/drivers/net/wireless/ath/wcn36xx/dxe.c
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.c
@@ -79,6 +79,7 @@ static int wcn36xx_dxe_allocate_ctl_block(struct wcn36xx_dxe_ch *ch)
79 struct wcn36xx_dxe_ctl *cur_ctl = NULL; 79 struct wcn36xx_dxe_ctl *cur_ctl = NULL;
80 int i; 80 int i;
81 81
82 spin_lock_init(&ch->lock);
82 for (i = 0; i < ch->desc_num; i++) { 83 for (i = 0; i < ch->desc_num; i++) {
83 cur_ctl = kzalloc(sizeof(*cur_ctl), GFP_KERNEL); 84 cur_ctl = kzalloc(sizeof(*cur_ctl), GFP_KERNEL);
84 if (!cur_ctl) 85 if (!cur_ctl)
@@ -345,7 +346,7 @@ void wcn36xx_dxe_tx_ack_ind(struct wcn36xx *wcn, u32 status)
345 346
346static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch) 347static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
347{ 348{
348 struct wcn36xx_dxe_ctl *ctl = ch->tail_blk_ctl; 349 struct wcn36xx_dxe_ctl *ctl;
349 struct ieee80211_tx_info *info; 350 struct ieee80211_tx_info *info;
350 unsigned long flags; 351 unsigned long flags;
351 352
@@ -354,6 +355,8 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
354 * completely full head and tail are pointing to the same element 355 * completely full head and tail are pointing to the same element
355 * and while-do will not make any cycles. 356 * and while-do will not make any cycles.
356 */ 357 */
358 spin_lock_irqsave(&ch->lock, flags);
359 ctl = ch->tail_blk_ctl;
357 do { 360 do {
358 if (ctl->desc->ctrl & WCN36XX_DXE_CTRL_VALID_MASK) 361 if (ctl->desc->ctrl & WCN36XX_DXE_CTRL_VALID_MASK)
359 break; 362 break;
@@ -365,12 +368,12 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
365 /* Keep frame until TX status comes */ 368 /* Keep frame until TX status comes */
366 ieee80211_free_txskb(wcn->hw, ctl->skb); 369 ieee80211_free_txskb(wcn->hw, ctl->skb);
367 } 370 }
368 spin_lock_irqsave(&ctl->skb_lock, flags); 371 spin_lock(&ctl->skb_lock);
369 if (wcn->queues_stopped) { 372 if (wcn->queues_stopped) {
370 wcn->queues_stopped = false; 373 wcn->queues_stopped = false;
371 ieee80211_wake_queues(wcn->hw); 374 ieee80211_wake_queues(wcn->hw);
372 } 375 }
373 spin_unlock_irqrestore(&ctl->skb_lock, flags); 376 spin_unlock(&ctl->skb_lock);
374 377
375 ctl->skb = NULL; 378 ctl->skb = NULL;
376 } 379 }
@@ -379,6 +382,7 @@ static void reap_tx_dxes(struct wcn36xx *wcn, struct wcn36xx_dxe_ch *ch)
379 !(ctl->desc->ctrl & WCN36XX_DXE_CTRL_VALID_MASK)); 382 !(ctl->desc->ctrl & WCN36XX_DXE_CTRL_VALID_MASK));
380 383
381 ch->tail_blk_ctl = ctl; 384 ch->tail_blk_ctl = ctl;
385 spin_unlock_irqrestore(&ch->lock, flags);
382} 386}
383 387
384static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev) 388static irqreturn_t wcn36xx_irq_tx_complete(int irq, void *dev)
@@ -596,12 +600,14 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
596 struct wcn36xx_dxe_desc *desc = NULL; 600 struct wcn36xx_dxe_desc *desc = NULL;
597 struct wcn36xx_dxe_ch *ch = NULL; 601 struct wcn36xx_dxe_ch *ch = NULL;
598 unsigned long flags; 602 unsigned long flags;
603 int ret;
599 604
600 ch = is_low ? &wcn->dxe_tx_l_ch : &wcn->dxe_tx_h_ch; 605 ch = is_low ? &wcn->dxe_tx_l_ch : &wcn->dxe_tx_h_ch;
601 606
607 spin_lock_irqsave(&ch->lock, flags);
602 ctl = ch->head_blk_ctl; 608 ctl = ch->head_blk_ctl;
603 609
604 spin_lock_irqsave(&ctl->next->skb_lock, flags); 610 spin_lock(&ctl->next->skb_lock);
605 611
606 /* 612 /*
607 * If skb is not null that means that we reached the tail of the ring 613 * If skb is not null that means that we reached the tail of the ring
@@ -611,10 +617,11 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
611 if (NULL != ctl->next->skb) { 617 if (NULL != ctl->next->skb) {
612 ieee80211_stop_queues(wcn->hw); 618 ieee80211_stop_queues(wcn->hw);
613 wcn->queues_stopped = true; 619 wcn->queues_stopped = true;
614 spin_unlock_irqrestore(&ctl->next->skb_lock, flags); 620 spin_unlock(&ctl->next->skb_lock);
621 spin_unlock_irqrestore(&ch->lock, flags);
615 return -EBUSY; 622 return -EBUSY;
616 } 623 }
617 spin_unlock_irqrestore(&ctl->next->skb_lock, flags); 624 spin_unlock(&ctl->next->skb_lock);
618 625
619 ctl->skb = NULL; 626 ctl->skb = NULL;
620 desc = ctl->desc; 627 desc = ctl->desc;
@@ -640,7 +647,8 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
640 desc = ctl->desc; 647 desc = ctl->desc;
641 if (ctl->bd_cpu_addr) { 648 if (ctl->bd_cpu_addr) {
642 wcn36xx_err("bd_cpu_addr cannot be NULL for skb DXE\n"); 649 wcn36xx_err("bd_cpu_addr cannot be NULL for skb DXE\n");
643 return -EINVAL; 650 ret = -EINVAL;
651 goto unlock;
644 } 652 }
645 653
646 desc->src_addr_l = dma_map_single(NULL, 654 desc->src_addr_l = dma_map_single(NULL,
@@ -679,7 +687,10 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
679 ch->reg_ctrl, ch->def_ctrl); 687 ch->reg_ctrl, ch->def_ctrl);
680 } 688 }
681 689
682 return 0; 690 ret = 0;
691unlock:
692 spin_unlock_irqrestore(&ch->lock, flags);
693 return ret;
683} 694}
684 695
685int wcn36xx_dxe_init(struct wcn36xx *wcn) 696int wcn36xx_dxe_init(struct wcn36xx *wcn)
diff --git a/drivers/net/wireless/ath/wcn36xx/dxe.h b/drivers/net/wireless/ath/wcn36xx/dxe.h
index 35ee7e966bd2..3eca4f9594f2 100644
--- a/drivers/net/wireless/ath/wcn36xx/dxe.h
+++ b/drivers/net/wireless/ath/wcn36xx/dxe.h
@@ -243,6 +243,7 @@ struct wcn36xx_dxe_ctl {
243}; 243};
244 244
245struct wcn36xx_dxe_ch { 245struct wcn36xx_dxe_ch {
246 spinlock_t lock; /* protects head/tail ptrs */
246 enum wcn36xx_dxe_ch_type ch_type; 247 enum wcn36xx_dxe_ch_type ch_type;
247 void *cpu_addr; 248 void *cpu_addr;
248 dma_addr_t dma_addr; 249 dma_addr_t dma_addr;