diff options
Diffstat (limited to 'drivers/net/wireless/wl12xx/tx.c')
| -rw-r--r-- | drivers/net/wireless/wl12xx/tx.c | 234 | 
1 files changed, 176 insertions, 58 deletions
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c index 5e9ef7d53e7..7a3339fd341 100644 --- a/drivers/net/wireless/wl12xx/tx.c +++ b/drivers/net/wireless/wl12xx/tx.c @@ -70,6 +70,28 @@ static void wl1271_free_tx_id(struct wl1271 *wl, int id)  	}  } +static int wl1271_tx_update_filters(struct wl1271 *wl, +						 struct sk_buff *skb) +{ +	struct ieee80211_hdr *hdr; + +	hdr = (struct ieee80211_hdr *)(skb->data + +				       sizeof(struct wl1271_tx_hw_descr)); + +	/* +	 * stop bssid-based filtering before transmitting authentication +	 * requests. this way the hw will never drop authentication +	 * responses coming from BSSIDs it isn't familiar with (e.g. on +	 * roaming) +	 */ +	if (!ieee80211_is_auth(hdr->frame_control)) +		return 0; + +	wl1271_configure_filters(wl, FIF_OTHER_BSS); + +	return wl1271_acx_rx_config(wl, wl->rx_config, wl->rx_filter); +} +  static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl,  						 struct sk_buff *skb)  { @@ -127,13 +149,29 @@ u8 wl1271_tx_get_hlid(struct sk_buff *skb)  	}  } +static unsigned int wl12xx_calc_packet_alignment(struct wl1271 *wl, +						unsigned int packet_length) +{ +	if (wl->quirks & WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT) +		return ALIGN(packet_length, WL12XX_BUS_BLOCK_SIZE); +	else +		return ALIGN(packet_length, WL1271_TX_ALIGN_TO); +} +  static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,  				u32 buf_offset, u8 hlid)  {  	struct wl1271_tx_hw_descr *desc;  	u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; +	u32 len;  	u32 total_blocks;  	int id, ret = -EBUSY; +	u32 spare_blocks; + +	if (unlikely(wl->quirks & WL12XX_QUIRK_USE_2_SPARE_BLOCKS)) +		spare_blocks = 2; +	else +		spare_blocks = 1;  	if (buf_offset + total_len > WL1271_AGGR_BUFFER_SIZE)  		return -EAGAIN; @@ -145,17 +183,27 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,  	/* approximate the number of blocks required for this packet  	   in the firmware */ -	total_blocks = total_len + TX_HW_BLOCK_SIZE - 1; -	total_blocks = total_blocks / TX_HW_BLOCK_SIZE + TX_HW_BLOCK_SPARE; +	len = wl12xx_calc_packet_alignment(wl, total_len); + +	total_blocks = (len + TX_HW_BLOCK_SIZE - 1) / TX_HW_BLOCK_SIZE + +		spare_blocks; +  	if (total_blocks <= wl->tx_blocks_available) {  		desc = (struct wl1271_tx_hw_descr *)skb_push(  			skb, total_len - skb->len); -		desc->extra_mem_blocks = TX_HW_BLOCK_SPARE; -		desc->total_mem_blocks = total_blocks; +		/* HW descriptor fields change between wl127x and wl128x */ +		if (wl->chip.id == CHIP_ID_1283_PG20) { +			desc->wl128x_mem.total_mem_blocks = total_blocks; +		} else { +			desc->wl127x_mem.extra_blocks = spare_blocks; +			desc->wl127x_mem.total_mem_blocks = total_blocks; +		} +  		desc->id = id;  		wl->tx_blocks_available -= total_blocks; +		wl->tx_allocated_blocks += total_blocks;  		if (wl->bss_type == BSS_TYPE_AP_BSS)  			wl->links[hlid].allocated_blks += total_blocks; @@ -172,13 +220,18 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,  	return ret;  } +static bool wl12xx_is_dummy_packet(struct wl1271 *wl, struct sk_buff *skb) +{ +	return wl->dummy_packet == skb; +} +  static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,  			      u32 extra, struct ieee80211_tx_info *control,  			      u8 hlid)  {  	struct timespec ts;  	struct wl1271_tx_hw_descr *desc; -	int pad, ac, rate_idx; +	int aligned_len, ac, rate_idx;  	s64 hosttime;  	u16 tx_attr; @@ -202,12 +255,25 @@ static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,  	else  		desc->life_time = cpu_to_le16(TX_HW_AP_MODE_PKT_LIFETIME_TU); -	/* configure the tx attributes */ -	tx_attr = wl->session_counter << TX_HW_ATTR_OFST_SESSION_COUNTER; - -	/* queue (we use same identifiers for tid's and ac's */ +	/* queue */  	ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); -	desc->tid = ac; +	desc->tid = skb->priority; + +	if (wl12xx_is_dummy_packet(wl, skb)) { +		/* +		 * FW expects the dummy packet to have an invalid session id - +		 * any session id that is different than the one set in the join +		 */ +		tx_attr = ((~wl->session_counter) << +			   TX_HW_ATTR_OFST_SESSION_COUNTER) & +			   TX_HW_ATTR_SESSION_COUNTER; + +		tx_attr |= TX_HW_ATTR_TX_DUMMY_REQ; +	} else { +		/* configure the tx attributes */ +		tx_attr = +			wl->session_counter << TX_HW_ATTR_OFST_SESSION_COUNTER; +	}  	if (wl->bss_type != BSS_TYPE_AP_BSS) {  		desc->aid = hlid; @@ -237,20 +303,37 @@ static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,  	tx_attr |= rate_idx << TX_HW_ATTR_OFST_RATE_POLICY;  	desc->reserved = 0; -	/* align the length (and store in terms of words) */ -	pad = ALIGN(skb->len, WL1271_TX_ALIGN_TO); -	desc->length = cpu_to_le16(pad >> 2); +	aligned_len = wl12xx_calc_packet_alignment(wl, skb->len); -	/* calculate number of padding bytes */ -	pad = pad - skb->len; -	tx_attr |= pad << TX_HW_ATTR_OFST_LAST_WORD_PAD; +	if (wl->chip.id == CHIP_ID_1283_PG20) { +		desc->wl128x_mem.extra_bytes = aligned_len - skb->len; +		desc->length = cpu_to_le16(aligned_len >> 2); -	desc->tx_attr = cpu_to_le16(tx_attr); +		wl1271_debug(DEBUG_TX, "tx_fill_hdr: hlid: %d " +			     "tx_attr: 0x%x len: %d life: %d mem: %d", +			     desc->hlid, tx_attr, +			     le16_to_cpu(desc->length), +			     le16_to_cpu(desc->life_time), +			     desc->wl128x_mem.total_mem_blocks); +	} else { +		int pad; + +		/* Store the aligned length in terms of words */ +		desc->length = cpu_to_le16(aligned_len >> 2); + +		/* calculate number of padding bytes */ +		pad = aligned_len - skb->len; +		tx_attr |= pad << TX_HW_ATTR_OFST_LAST_WORD_PAD; -	wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d hlid: %d " -		"tx_attr: 0x%x len: %d life: %d mem: %d", pad, desc->hlid, -		le16_to_cpu(desc->tx_attr), le16_to_cpu(desc->length), -		le16_to_cpu(desc->life_time), desc->total_mem_blocks); +		wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d hlid: %d " +			     "tx_attr: 0x%x len: %d life: %d mem: %d", pad, +			     desc->hlid, tx_attr, +			     le16_to_cpu(desc->length), +			     le16_to_cpu(desc->life_time), +			     desc->wl127x_mem.total_mem_blocks); +	} + +	desc->tx_attr = cpu_to_le16(tx_attr);  }  /* caller must hold wl->mutex */ @@ -300,19 +383,29 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,  	if (wl->bss_type == BSS_TYPE_AP_BSS) {  		wl1271_tx_ap_update_inconnection_sta(wl, skb);  		wl1271_tx_regulate_link(wl, hlid); +	} else { +		wl1271_tx_update_filters(wl, skb);  	}  	wl1271_tx_fill_hdr(wl, skb, extra, info, hlid);  	/* -	 * The length of each packet is stored in terms of words. Thus, we must -	 * pad the skb data to make sure its length is aligned. -	 * The number of padding bytes is computed and set in wl1271_tx_fill_hdr +	 * The length of each packet is stored in terms of +	 * words. Thus, we must pad the skb data to make sure its +	 * length is aligned.  The number of padding bytes is computed +	 * and set in wl1271_tx_fill_hdr. +	 * In special cases, we want to align to a specific block size +	 * (eg. for wl128x with SDIO we align to 256).  	 */ -	total_len = ALIGN(skb->len, WL1271_TX_ALIGN_TO); +	total_len = wl12xx_calc_packet_alignment(wl, skb->len); +  	memcpy(wl->aggr_buf + buf_offset, skb->data, skb->len);  	memset(wl->aggr_buf + buf_offset + skb->len, 0, total_len - skb->len); +	/* Revert side effects in the dummy packet skb, so it can be reused */ +	if (wl12xx_is_dummy_packet(wl, skb)) +		skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); +  	return total_len;  } @@ -425,10 +518,23 @@ out:  static struct sk_buff *wl1271_skb_dequeue(struct wl1271 *wl)  { +	unsigned long flags; +	struct sk_buff *skb = NULL; +  	if (wl->bss_type == BSS_TYPE_AP_BSS) -		return wl1271_ap_skb_dequeue(wl); +		skb = wl1271_ap_skb_dequeue(wl); +	else +		skb = wl1271_sta_skb_dequeue(wl); + +	if (!skb && +	    test_and_clear_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags)) { +		skb = wl->dummy_packet; +		spin_lock_irqsave(&wl->wl_lock, flags); +		wl->tx_queue_count--; +		spin_unlock_irqrestore(&wl->wl_lock, flags); +	} -	return wl1271_sta_skb_dequeue(wl); +	return skb;  }  static void wl1271_skb_queue_head(struct wl1271 *wl, struct sk_buff *skb) @@ -436,7 +542,9 @@ static void wl1271_skb_queue_head(struct wl1271 *wl, struct sk_buff *skb)  	unsigned long flags;  	int q = wl1271_tx_get_queue(skb_get_queue_mapping(skb)); -	if (wl->bss_type == BSS_TYPE_AP_BSS) { +	if (wl12xx_is_dummy_packet(wl, skb)) { +		set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags); +	} else if (wl->bss_type == BSS_TYPE_AP_BSS) {  		u8 hlid = wl1271_tx_get_hlid(skb);  		skb_queue_head(&wl->links[hlid].tx_queue[q], skb); @@ -454,22 +562,14 @@ static void wl1271_skb_queue_head(struct wl1271 *wl, struct sk_buff *skb)  void wl1271_tx_work_locked(struct wl1271 *wl)  {  	struct sk_buff *skb; -	bool woken_up = false;  	u32 buf_offset = 0;  	bool sent_packets = false;  	int ret;  	if (unlikely(wl->state == WL1271_STATE_OFF)) -		goto out; +		return;  	while ((skb = wl1271_skb_dequeue(wl))) { -		if (!woken_up) { -			ret = wl1271_ps_elp_wakeup(wl); -			if (ret < 0) -				goto out_ack; -			woken_up = true; -		} -  		ret = wl1271_prepare_tx_frame(wl, skb, buf_offset);  		if (ret == -EAGAIN) {  			/* @@ -516,18 +616,22 @@ out_ack:  		wl1271_handle_tx_low_watermark(wl);  	} - -out: -	if (woken_up) -		wl1271_ps_elp_sleep(wl);  }  void wl1271_tx_work(struct work_struct *work)  {  	struct wl1271 *wl = container_of(work, struct wl1271, tx_work); +	int ret;  	mutex_lock(&wl->mutex); +	ret = wl1271_ps_elp_wakeup(wl); +	if (ret < 0) +		goto out; +  	wl1271_tx_work_locked(wl); + +	wl1271_ps_elp_wakeup(wl); +out:  	mutex_unlock(&wl->mutex);  } @@ -549,6 +653,11 @@ static void wl1271_tx_complete_packet(struct wl1271 *wl,  	skb = wl->tx_frames[id];  	info = IEEE80211_SKB_CB(skb); +	if (wl12xx_is_dummy_packet(wl, skb)) { +		wl1271_free_tx_id(wl, id); +		return; +	} +  	/* update the TX status info */  	if (result->status == TX_SUCCESS) {  		if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) @@ -678,10 +787,13 @@ void wl1271_tx_reset(struct wl1271 *wl)  			while ((skb = skb_dequeue(&wl->tx_queue[i]))) {  				wl1271_debug(DEBUG_TX, "freeing skb 0x%p",  					     skb); -				info = IEEE80211_SKB_CB(skb); -				info->status.rates[0].idx = -1; -				info->status.rates[0].count = 0; -				ieee80211_tx_status(wl->hw, skb); + +				if (!wl12xx_is_dummy_packet(wl, skb)) { +					info = IEEE80211_SKB_CB(skb); +					info->status.rates[0].idx = -1; +					info->status.rates[0].count = 0; +					ieee80211_tx_status(wl->hw, skb); +				}  			}  		}  	} @@ -702,21 +814,27 @@ void wl1271_tx_reset(struct wl1271 *wl)  		wl1271_free_tx_id(wl, i);  		wl1271_debug(DEBUG_TX, "freeing skb 0x%p", skb); -		/* Remove private headers before passing the skb to mac80211 */ -		info = IEEE80211_SKB_CB(skb); -		skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); -		if (info->control.hw_key && -		    info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) { -			int hdrlen = ieee80211_get_hdrlen_from_skb(skb); -			memmove(skb->data + WL1271_TKIP_IV_SPACE, skb->data, -				hdrlen); -			skb_pull(skb, WL1271_TKIP_IV_SPACE); -		} +		if (!wl12xx_is_dummy_packet(wl, skb)) { +			/* +			 * Remove private headers before passing the skb to +			 * mac80211 +			 */ +			info = IEEE80211_SKB_CB(skb); +			skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); +			if (info->control.hw_key && +			    info->control.hw_key->cipher == +			    WLAN_CIPHER_SUITE_TKIP) { +				int hdrlen = ieee80211_get_hdrlen_from_skb(skb); +				memmove(skb->data + WL1271_TKIP_IV_SPACE, +					skb->data, hdrlen); +				skb_pull(skb, WL1271_TKIP_IV_SPACE); +			} -		info->status.rates[0].idx = -1; -		info->status.rates[0].count = 0; +			info->status.rates[0].idx = -1; +			info->status.rates[0].count = 0; -		ieee80211_tx_status(wl->hw, skb); +			ieee80211_tx_status(wl->hw, skb); +		}  	}  }  |