diff options
Diffstat (limited to 'drivers/net/wireless/mwl8k.c')
| -rw-r--r-- | drivers/net/wireless/mwl8k.c | 867 | 
1 files changed, 794 insertions, 73 deletions
diff --git a/drivers/net/wireless/mwl8k.c b/drivers/net/wireless/mwl8k.c index c1ceb4b2397..9f5ecef297e 100644 --- a/drivers/net/wireless/mwl8k.c +++ b/drivers/net/wireless/mwl8k.c @@ -63,6 +63,7 @@ MODULE_PARM_DESC(ap_mode_default,  #define MWL8K_HIU_A2H_INTERRUPT_CLEAR_SEL	0x00000c38  #define MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK	0x00000c3c  #define  MWL8K_A2H_INT_DUMMY			 (1 << 20) +#define  MWL8K_A2H_INT_BA_WATCHDOG		 (1 << 14)  #define  MWL8K_A2H_INT_CHNL_SWITCHED		 (1 << 11)  #define  MWL8K_A2H_INT_QUEUE_EMPTY		 (1 << 10)  #define  MWL8K_A2H_INT_RADAR_DETECT		 (1 << 7) @@ -73,6 +74,14 @@ MODULE_PARM_DESC(ap_mode_default,  #define  MWL8K_A2H_INT_RX_READY			 (1 << 1)  #define  MWL8K_A2H_INT_TX_DONE			 (1 << 0) +/* HW micro second timer register + * located at offset 0xA600. This + * will be used to timestamp tx + * packets. + */ + +#define	MWL8K_HW_TIMER_REGISTER			0x0000a600 +  #define MWL8K_A2H_EVENTS	(MWL8K_A2H_INT_DUMMY | \  				 MWL8K_A2H_INT_CHNL_SWITCHED | \  				 MWL8K_A2H_INT_QUEUE_EMPTY | \ @@ -82,10 +91,14 @@ MODULE_PARM_DESC(ap_mode_default,  				 MWL8K_A2H_INT_MAC_EVENT | \  				 MWL8K_A2H_INT_OPC_DONE | \  				 MWL8K_A2H_INT_RX_READY | \ -				 MWL8K_A2H_INT_TX_DONE) +				 MWL8K_A2H_INT_TX_DONE | \ +				 MWL8K_A2H_INT_BA_WATCHDOG)  #define MWL8K_RX_QUEUES		1 -#define MWL8K_TX_QUEUES		4 +#define MWL8K_TX_WMM_QUEUES	4 +#define MWL8K_MAX_AMPDU_QUEUES	8 +#define MWL8K_MAX_TX_QUEUES	(MWL8K_TX_WMM_QUEUES + MWL8K_MAX_AMPDU_QUEUES) +#define mwl8k_tx_queues(priv)	(MWL8K_TX_WMM_QUEUES + (priv)->num_ampdu_queues)  struct rxd_ops {  	int rxd_size; @@ -134,6 +147,21 @@ struct mwl8k_tx_queue {  	struct sk_buff **skb;  }; +enum { +	AMPDU_NO_STREAM, +	AMPDU_STREAM_NEW, +	AMPDU_STREAM_IN_PROGRESS, +	AMPDU_STREAM_ACTIVE, +}; + +struct mwl8k_ampdu_stream { +	struct ieee80211_sta *sta; +	u8 tid; +	u8 state; +	u8 idx; +	u8 txq_idx; /* index of this stream in priv->txq */ +}; +  struct mwl8k_priv {  	struct ieee80211_hw *hw;  	struct pci_dev *pdev; @@ -160,6 +188,12 @@ struct mwl8k_priv {  	u32 ap_macids_supported;  	u32 sta_macids_supported; +	/* Ampdu stream information */ +	u8 num_ampdu_queues; +	spinlock_t stream_lock; +	struct mwl8k_ampdu_stream ampdu[MWL8K_MAX_AMPDU_QUEUES]; +	struct work_struct watchdog_ba_handle; +  	/* firmware access */  	struct mutex fw_mutex;  	struct task_struct *fw_mutex_owner; @@ -191,7 +225,8 @@ struct mwl8k_priv {  	int pending_tx_pkts;  	struct mwl8k_rx_queue rxq[MWL8K_RX_QUEUES]; -	struct mwl8k_tx_queue txq[MWL8K_TX_QUEUES]; +	struct mwl8k_tx_queue txq[MWL8K_MAX_TX_QUEUES]; +	u32 txq_offset[MWL8K_MAX_TX_QUEUES];  	bool radio_on;  	bool radio_short_preamble; @@ -224,7 +259,7 @@ struct mwl8k_priv {  	 * preserve the queue configurations so they can be restored if/when  	 * the firmware image is swapped.  	 */ -	struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_QUEUES]; +	struct ieee80211_tx_queue_params wmm_params[MWL8K_TX_WMM_QUEUES];  	/* async firmware loading state */  	unsigned fw_state; @@ -262,9 +297,17 @@ struct mwl8k_vif {  #define MWL8K_VIF(_vif) ((struct mwl8k_vif *)&((_vif)->drv_priv))  #define IEEE80211_KEY_CONF(_u8) ((struct ieee80211_key_conf *)(_u8)) +struct tx_traffic_info { +	u32 start_time; +	u32 pkts; +}; + +#define MWL8K_MAX_TID 8  struct mwl8k_sta {  	/* Index into station database. Returned by UPDATE_STADB.  */  	u8 peer_id; +	u8 is_ampdu_allowed; +	struct tx_traffic_info tx_stats[MWL8K_MAX_TID];  };  #define MWL8K_STA(_sta) ((struct mwl8k_sta *)&((_sta)->drv_priv)) @@ -352,10 +395,12 @@ static const struct ieee80211_rate mwl8k_rates_50[] = {  #define MWL8K_CMD_ENABLE_SNIFFER	0x0150  #define MWL8K_CMD_SET_MAC_ADDR		0x0202		/* per-vif */  #define MWL8K_CMD_SET_RATEADAPT_MODE	0x0203 +#define MWL8K_CMD_GET_WATCHDOG_BITMAP	0x0205  #define MWL8K_CMD_BSS_START		0x1100		/* per-vif */  #define MWL8K_CMD_SET_NEW_STN		0x1111		/* per-vif */  #define MWL8K_CMD_UPDATE_ENCRYPTION	0x1122		/* per-vif */  #define MWL8K_CMD_UPDATE_STADB		0x1123 +#define MWL8K_CMD_BASTREAM		0x1125  static const char *mwl8k_cmd_name(__le16 cmd, char *buf, int bufsize)  { @@ -395,6 +440,8 @@ static const char *mwl8k_cmd_name(__le16 cmd, char *buf, int bufsize)  		MWL8K_CMDNAME(SET_NEW_STN);  		MWL8K_CMDNAME(UPDATE_ENCRYPTION);  		MWL8K_CMDNAME(UPDATE_STADB); +		MWL8K_CMDNAME(BASTREAM); +		MWL8K_CMDNAME(GET_WATCHDOG_BITMAP);  	default:  		snprintf(buf, bufsize, "0x%x", cmd);  	} @@ -669,7 +716,7 @@ static int mwl8k_load_firmware(struct ieee80211_hw *hw)  			       "helper image\n", pci_name(priv->pdev));  			return rc;  		} -		msleep(5); +		msleep(20);  		rc = mwl8k_feed_fw_image(priv, fw->data, fw->size);  	} else { @@ -734,8 +781,10 @@ static inline void mwl8k_remove_dma_header(struct sk_buff *skb, __le16 qos)  		skb_pull(skb, sizeof(*tr) - hdrlen);  } +#define REDUCED_TX_HEADROOM	8 +  static void -mwl8k_add_dma_header(struct sk_buff *skb, int tail_pad) +mwl8k_add_dma_header(struct mwl8k_priv *priv, struct sk_buff *skb, int tail_pad)  {  	struct ieee80211_hdr *wh;  	int hdrlen; @@ -751,6 +800,22 @@ mwl8k_add_dma_header(struct sk_buff *skb, int tail_pad)  	wh = (struct ieee80211_hdr *)skb->data;  	hdrlen = ieee80211_hdrlen(wh->frame_control); + +	/* +	 * Check if skb_resize is required because of +	 * tx_headroom adjustment. +	 */ +	if (priv->ap_fw && (hdrlen < (sizeof(struct ieee80211_cts) +						+ REDUCED_TX_HEADROOM))) { +		if (pskb_expand_head(skb, REDUCED_TX_HEADROOM, 0, GFP_ATOMIC)) { + +			wiphy_err(priv->hw->wiphy, +					"Failed to reallocate TX buffer\n"); +			return; +		} +		skb->truesize += REDUCED_TX_HEADROOM; +	} +  	reqd_hdrlen = sizeof(*tr);  	if (hdrlen != reqd_hdrlen) @@ -773,7 +838,8 @@ mwl8k_add_dma_header(struct sk_buff *skb, int tail_pad)  	tr->fwlen = cpu_to_le16(skb->len - sizeof(*tr) + tail_pad);  } -static void mwl8k_encapsulate_tx_frame(struct sk_buff *skb) +static void mwl8k_encapsulate_tx_frame(struct mwl8k_priv *priv, +		struct sk_buff *skb)  {  	struct ieee80211_hdr *wh;  	struct ieee80211_tx_info *tx_info; @@ -791,8 +857,8 @@ static void mwl8k_encapsulate_tx_frame(struct sk_buff *skb)  	/*  	 * Make sure the packet header is in the DMA header format (4-address  	 * without QoS), the necessary crypto padding between the header and the -	 * payload has already been provided by mac80211, but it doesn't add tail -	 * padding when HW crypto is enabled. +	 * payload has already been provided by mac80211, but it doesn't add +	 * tail padding when HW crypto is enabled.  	 *  	 * We have the following trailer padding requirements:  	 * - WEP: 4 trailer bytes (ICV) @@ -814,7 +880,7 @@ static void mwl8k_encapsulate_tx_frame(struct sk_buff *skb)  			break;  		}  	} -	mwl8k_add_dma_header(skb, data_pad); +	mwl8k_add_dma_header(priv, skb, data_pad);  }  /* @@ -1127,6 +1193,9 @@ static void mwl8k_rxq_deinit(struct ieee80211_hw *hw, int index)  	struct mwl8k_rx_queue *rxq = priv->rxq + index;  	int i; +	if (rxq->rxd == NULL) +		return; +  	for (i = 0; i < MWL8K_RX_DESCS; i++) {  		if (rxq->buf[i].skb != NULL) {  			pci_unmap_single(priv->pdev, @@ -1319,7 +1388,7 @@ struct mwl8k_tx_desc {  	__le16 pkt_len;  	__u8 dest_MAC_addr[ETH_ALEN];  	__le32 next_txd_phys_addr; -	__le32 reserved; +	__le32 timestamp;  	__le16 rate_info;  	__u8 peer_id;  	__u8 tx_frag_cnt; @@ -1383,7 +1452,7 @@ static void mwl8k_dump_tx_rings(struct ieee80211_hw *hw)  	struct mwl8k_priv *priv = hw->priv;  	int i; -	for (i = 0; i < MWL8K_TX_QUEUES; i++) { +	for (i = 0; i < mwl8k_tx_queues(priv); i++) {  		struct mwl8k_tx_queue *txq = priv->txq + i;  		int fw_owned = 0;  		int drv_owned = 0; @@ -1452,9 +1521,8 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)  		if (timeout) {  			WARN_ON(priv->pending_tx_pkts); -			if (retry) { +			if (retry)  				wiphy_notice(hw->wiphy, "tx rings drained\n"); -			}  			break;  		} @@ -1484,6 +1552,41 @@ static int mwl8k_tx_wait_empty(struct ieee80211_hw *hw)  		     MWL8K_TXD_STATUS_OK_RETRY |		\  		     MWL8K_TXD_STATUS_OK_MORE_RETRY)) +static int mwl8k_tid_queue_mapping(u8 tid) +{ +	BUG_ON(tid > 7); + +	switch (tid) { +	case 0: +	case 3: +		return IEEE80211_AC_BE; +		break; +	case 1: +	case 2: +		return IEEE80211_AC_BK; +		break; +	case 4: +	case 5: +		return IEEE80211_AC_VI; +		break; +	case 6: +	case 7: +		return IEEE80211_AC_VO; +		break; +	default: +		return -1; +		break; +	} +} + +/* The firmware will fill in the rate information + * for each packet that gets queued in the hardware + * and these macros will interpret that info. + */ + +#define RI_FORMAT(a)		  (a & 0x0001) +#define RI_RATE_ID_MCS(a)	 ((a & 0x01f8) >> 3) +  static int  mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)  { @@ -1500,6 +1603,10 @@ mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)  		struct sk_buff *skb;  		struct ieee80211_tx_info *info;  		u32 status; +		struct ieee80211_sta *sta; +		struct mwl8k_sta *sta_info = NULL; +		u16 rate_info; +		struct ieee80211_hdr *wh;  		tx = txq->head;  		tx_desc = txq->txd + tx; @@ -1528,18 +1635,40 @@ mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)  		mwl8k_remove_dma_header(skb, tx_desc->qos_control); +		wh = (struct ieee80211_hdr *) skb->data; +  		/* Mark descriptor as unused */  		tx_desc->pkt_phys_addr = 0;  		tx_desc->pkt_len = 0;  		info = IEEE80211_SKB_CB(skb); +		if (ieee80211_is_data(wh->frame_control)) { +			sta = info->control.sta; +			if (sta) { +				sta_info = MWL8K_STA(sta); +				BUG_ON(sta_info == NULL); +				rate_info = le16_to_cpu(tx_desc->rate_info); +				/* If rate is < 6.5 Mpbs for an ht station +				 * do not form an ampdu. If the station is a +				 * legacy station (format = 0), do not form an +				 * ampdu +				 */ +				if (RI_RATE_ID_MCS(rate_info) < 1 || +				    RI_FORMAT(rate_info) == 0) { +					sta_info->is_ampdu_allowed = false; +				} else { +					sta_info->is_ampdu_allowed = true; +				} +			} +		} +  		ieee80211_tx_info_clear_status(info);  		/* Rate control is happening in the firmware.  		 * Ensure no tx rate is being reported.  		 */ -                info->status.rates[0].idx = -1; -                info->status.rates[0].count = 1; +		info->status.rates[0].idx = -1; +		info->status.rates[0].count = 1;  		if (MWL8K_TXD_SUCCESS(status))  			info->flags |= IEEE80211_TX_STAT_ACK; @@ -1549,9 +1678,6 @@ mwl8k_txq_reclaim(struct ieee80211_hw *hw, int index, int limit, int force)  		processed++;  	} -	if (processed && priv->radio_on && !mutex_is_locked(&priv->fw_mutex)) -		ieee80211_wake_queue(hw, index); -  	return processed;  } @@ -1561,6 +1687,9 @@ static void mwl8k_txq_deinit(struct ieee80211_hw *hw, int index)  	struct mwl8k_priv *priv = hw->priv;  	struct mwl8k_tx_queue *txq = priv->txq + index; +	if (txq->txd == NULL) +		return; +  	mwl8k_txq_reclaim(hw, index, INT_MAX, 1);  	kfree(txq->skb); @@ -1572,12 +1701,116 @@ static void mwl8k_txq_deinit(struct ieee80211_hw *hw, int index)  	txq->txd = NULL;  } +/* caller must hold priv->stream_lock when calling the stream functions */ +static struct mwl8k_ampdu_stream * +mwl8k_add_stream(struct ieee80211_hw *hw, struct ieee80211_sta *sta, u8 tid) +{ +	struct mwl8k_ampdu_stream *stream; +	struct mwl8k_priv *priv = hw->priv; +	int i; + +	for (i = 0; i < priv->num_ampdu_queues; i++) { +		stream = &priv->ampdu[i]; +		if (stream->state == AMPDU_NO_STREAM) { +			stream->sta = sta; +			stream->state = AMPDU_STREAM_NEW; +			stream->tid = tid; +			stream->idx = i; +			stream->txq_idx = MWL8K_TX_WMM_QUEUES + i; +			wiphy_debug(hw->wiphy, "Added a new stream for %pM %d", +				    sta->addr, tid); +			return stream; +		} +	} +	return NULL; +} + +static int +mwl8k_start_stream(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream) +{ +	int ret; + +	/* if the stream has already been started, don't start it again */ +	if (stream->state != AMPDU_STREAM_NEW) +		return 0; +	ret = ieee80211_start_tx_ba_session(stream->sta, stream->tid, 0); +	if (ret) +		wiphy_debug(hw->wiphy, "Failed to start stream for %pM %d: " +			    "%d\n", stream->sta->addr, stream->tid, ret); +	else +		wiphy_debug(hw->wiphy, "Started stream for %pM %d\n", +			    stream->sta->addr, stream->tid); +	return ret; +} + +static void +mwl8k_remove_stream(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream) +{ +	wiphy_debug(hw->wiphy, "Remove stream for %pM %d\n", stream->sta->addr, +		    stream->tid); +	memset(stream, 0, sizeof(*stream)); +} + +static struct mwl8k_ampdu_stream * +mwl8k_lookup_stream(struct ieee80211_hw *hw, u8 *addr, u8 tid) +{ +	struct mwl8k_priv *priv = hw->priv; +	int i; + +	for (i = 0 ; i < priv->num_ampdu_queues; i++) { +		struct mwl8k_ampdu_stream *stream; +		stream = &priv->ampdu[i]; +		if (stream->state == AMPDU_NO_STREAM) +			continue; +		if (!memcmp(stream->sta->addr, addr, ETH_ALEN) && +		    stream->tid == tid) +			return stream; +	} +	return NULL; +} + +#define MWL8K_AMPDU_PACKET_THRESHOLD 64 +static inline bool mwl8k_ampdu_allowed(struct ieee80211_sta *sta, u8 tid) +{ +	struct mwl8k_sta *sta_info = MWL8K_STA(sta); +	struct tx_traffic_info *tx_stats; + +	BUG_ON(tid >= MWL8K_MAX_TID); +	tx_stats = &sta_info->tx_stats[tid]; + +	return sta_info->is_ampdu_allowed && +		tx_stats->pkts > MWL8K_AMPDU_PACKET_THRESHOLD; +} + +static inline void mwl8k_tx_count_packet(struct ieee80211_sta *sta, u8 tid) +{ +	struct mwl8k_sta *sta_info = MWL8K_STA(sta); +	struct tx_traffic_info *tx_stats; + +	BUG_ON(tid >= MWL8K_MAX_TID); +	tx_stats = &sta_info->tx_stats[tid]; + +	if (tx_stats->start_time == 0) +		tx_stats->start_time = jiffies; + +	/* reset the packet count after each second elapses.  If the number of +	 * packets ever exceeds the ampdu_min_traffic threshold, we will allow +	 * an ampdu stream to be started. +	 */ +	if (jiffies - tx_stats->start_time > HZ) { +		tx_stats->pkts = 0; +		tx_stats->start_time = 0; +	} else +		tx_stats->pkts++; +} +  static void  mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  {  	struct mwl8k_priv *priv = hw->priv;  	struct ieee80211_tx_info *tx_info;  	struct mwl8k_vif *mwl8k_vif; +	struct ieee80211_sta *sta;  	struct ieee80211_hdr *wh;  	struct mwl8k_tx_queue *txq;  	struct mwl8k_tx_desc *tx; @@ -1585,6 +1818,12 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  	u32 txstatus;  	u8 txdatarate;  	u16 qos; +	int txpriority; +	u8 tid = 0; +	struct mwl8k_ampdu_stream *stream = NULL; +	bool start_ba_session = false; +	bool mgmtframe = false; +	struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data;  	wh = (struct ieee80211_hdr *)skb->data;  	if (ieee80211_is_data_qos(wh->frame_control)) @@ -1592,14 +1831,18 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  	else  		qos = 0; +	if (ieee80211_is_mgmt(wh->frame_control)) +		mgmtframe = true; +  	if (priv->ap_fw) -		mwl8k_encapsulate_tx_frame(skb); +		mwl8k_encapsulate_tx_frame(priv, skb);  	else -		mwl8k_add_dma_header(skb, 0); +		mwl8k_add_dma_header(priv, skb, 0);  	wh = &((struct mwl8k_dma_data *)skb->data)->wh;  	tx_info = IEEE80211_SKB_CB(skb); +	sta = tx_info->control.sta;  	mwl8k_vif = MWL8K_VIF(tx_info->control.vif);  	if (tx_info->flags & IEEE80211_TX_CTL_ASSIGN_SEQ) { @@ -1627,12 +1870,91 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  			qos |= MWL8K_QOS_ACK_POLICY_NORMAL;  	} +	/* Queue ADDBA request in the respective data queue.  While setting up +	 * the ampdu stream, mac80211 queues further packets for that +	 * particular ra/tid pair.  However, packets piled up in the hardware +	 * for that ra/tid pair will still go out. ADDBA request and the +	 * related data packets going out from different queues asynchronously +	 * will cause a shift in the receiver window which might result in +	 * ampdu packets getting dropped at the receiver after the stream has +	 * been setup. +	 */ +	if (unlikely(ieee80211_is_action(wh->frame_control) && +	    mgmt->u.action.category == WLAN_CATEGORY_BACK && +	    mgmt->u.action.u.addba_req.action_code == WLAN_ACTION_ADDBA_REQ && +	    priv->ap_fw)) { +		u16 capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); +		tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; +		index = mwl8k_tid_queue_mapping(tid); +	} + +	txpriority = index; + +	if (ieee80211_is_data_qos(wh->frame_control) && +	    skb->protocol != cpu_to_be16(ETH_P_PAE) && +	    sta->ht_cap.ht_supported && priv->ap_fw) { +		tid = qos & 0xf; +		mwl8k_tx_count_packet(sta, tid); +		spin_lock(&priv->stream_lock); +		stream = mwl8k_lookup_stream(hw, sta->addr, tid); +		if (stream != NULL) { +			if (stream->state == AMPDU_STREAM_ACTIVE) { +				txpriority = stream->txq_idx; +				index = stream->txq_idx; +			} else if (stream->state == AMPDU_STREAM_NEW) { +				/* We get here if the driver sends us packets +				 * after we've initiated a stream, but before +				 * our ampdu_action routine has been called +				 * with IEEE80211_AMPDU_TX_START to get the SSN +				 * for the ADDBA request.  So this packet can +				 * go out with no risk of sequence number +				 * mismatch.  No special handling is required. +				 */ +			} else { +				/* Drop packets that would go out after the +				 * ADDBA request was sent but before the ADDBA +				 * response is received.  If we don't do this, +				 * the recipient would probably receive it +				 * after the ADDBA request with SSN 0.  This +				 * will cause the recipient's BA receive window +				 * to shift, which would cause the subsequent +				 * packets in the BA stream to be discarded. +				 * mac80211 queues our packets for us in this +				 * case, so this is really just a safety check. +				 */ +				wiphy_warn(hw->wiphy, +					   "Cannot send packet while ADDBA " +					   "dialog is underway.\n"); +				spin_unlock(&priv->stream_lock); +				dev_kfree_skb(skb); +				return; +			} +		} else { +			/* Defer calling mwl8k_start_stream so that the current +			 * skb can go out before the ADDBA request.  This +			 * prevents sequence number mismatch at the recepient +			 * as described above. +			 */ +			if (mwl8k_ampdu_allowed(sta, tid)) { +				stream = mwl8k_add_stream(hw, sta, tid); +				if (stream != NULL) +					start_ba_session = true; +			} +		} +		spin_unlock(&priv->stream_lock); +	} +  	dma = pci_map_single(priv->pdev, skb->data,  				skb->len, PCI_DMA_TODEVICE);  	if (pci_dma_mapping_error(priv->pdev, dma)) {  		wiphy_debug(hw->wiphy,  			    "failed to dma map skb, dropping TX frame.\n"); +		if (start_ba_session) { +			spin_lock(&priv->stream_lock); +			mwl8k_remove_stream(hw, stream); +			spin_unlock(&priv->stream_lock); +		}  		dev_kfree_skb(skb);  		return;  	} @@ -1641,12 +1963,34 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  	txq = priv->txq + index; +	/* Mgmt frames that go out frequently are probe +	 * responses. Other mgmt frames got out relatively +	 * infrequently. Hence reserve 2 buffers so that +	 * other mgmt frames do not get dropped due to an +	 * already queued probe response in one of the +	 * reserved buffers. +	 */ + +	if (txq->len >= MWL8K_TX_DESCS - 2) { +		if (mgmtframe == false || +			txq->len == MWL8K_TX_DESCS) { +			if (start_ba_session) { +				spin_lock(&priv->stream_lock); +				mwl8k_remove_stream(hw, stream); +				spin_unlock(&priv->stream_lock); +			} +			spin_unlock_bh(&priv->tx_lock); +			dev_kfree_skb(skb); +			return; +		} +	} +  	BUG_ON(txq->skb[txq->tail] != NULL);  	txq->skb[txq->tail] = skb;  	tx = txq->txd + txq->tail;  	tx->data_rate = txdatarate; -	tx->tx_priority = index; +	tx->tx_priority = txpriority;  	tx->qos_control = cpu_to_le16(qos);  	tx->pkt_phys_addr = cpu_to_le32(dma);  	tx->pkt_len = cpu_to_le16(skb->len); @@ -1655,6 +1999,11 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  		tx->peer_id = MWL8K_STA(tx_info->control.sta)->peer_id;  	else  		tx->peer_id = 0; + +	if (priv->ap_fw) +		tx->timestamp = cpu_to_le32(ioread32(priv->regs + +						MWL8K_HW_TIMER_REGISTER)); +  	wmb();  	tx->status = cpu_to_le32(MWL8K_TXD_STATUS_FW_OWNED | txstatus); @@ -1665,12 +2014,17 @@ mwl8k_txq_xmit(struct ieee80211_hw *hw, int index, struct sk_buff *skb)  	if (txq->tail == MWL8K_TX_DESCS)  		txq->tail = 0; -	if (txq->head == txq->tail) -		ieee80211_stop_queue(hw, index); -  	mwl8k_tx_start(priv);  	spin_unlock_bh(&priv->tx_lock); + +	/* Initiate the ampdu session here */ +	if (start_ba_session) { +		spin_lock(&priv->stream_lock); +		if (mwl8k_start_stream(hw, stream)) +			mwl8k_remove_stream(hw, stream); +		spin_unlock(&priv->stream_lock); +	}  } @@ -1868,7 +2222,7 @@ struct mwl8k_cmd_get_hw_spec_sta {  	__u8 mcs_bitmap[16];  	__le32 rx_queue_ptr;  	__le32 num_tx_queues; -	__le32 tx_queue_ptrs[MWL8K_TX_QUEUES]; +	__le32 tx_queue_ptrs[MWL8K_TX_WMM_QUEUES];  	__le32 caps2;  	__le32 num_tx_desc_per_queue;  	__le32 total_rxd; @@ -1974,8 +2328,8 @@ static int mwl8k_cmd_get_hw_spec_sta(struct ieee80211_hw *hw)  	memset(cmd->perm_addr, 0xff, sizeof(cmd->perm_addr));  	cmd->ps_cookie = cpu_to_le32(priv->cookie_dma);  	cmd->rx_queue_ptr = cpu_to_le32(priv->rxq[0].rxd_dma); -	cmd->num_tx_queues = cpu_to_le32(MWL8K_TX_QUEUES); -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	cmd->num_tx_queues = cpu_to_le32(mwl8k_tx_queues(priv)); +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		cmd->tx_queue_ptrs[i] = cpu_to_le32(priv->txq[i].txd_dma);  	cmd->num_tx_desc_per_queue = cpu_to_le32(MWL8K_TX_DESCS);  	cmd->total_rxd = cpu_to_le32(MWL8K_RX_DESCS); @@ -2017,13 +2371,16 @@ struct mwl8k_cmd_get_hw_spec_ap {  	__le32 wcbbase2;  	__le32 wcbbase3;  	__le32 fw_api_version; +	__le32 caps; +	__le32 num_of_ampdu_queues; +	__le32 wcbbase_ampdu[MWL8K_MAX_AMPDU_QUEUES];  } __packed;  static int mwl8k_cmd_get_hw_spec_ap(struct ieee80211_hw *hw)  {  	struct mwl8k_priv *priv = hw->priv;  	struct mwl8k_cmd_get_hw_spec_ap *cmd; -	int rc; +	int rc, i;  	u32 api_version;  	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); @@ -2055,27 +2412,31 @@ static int mwl8k_cmd_get_hw_spec_ap(struct ieee80211_hw *hw)  		priv->num_mcaddrs = le16_to_cpu(cmd->num_mcaddrs);  		priv->fw_rev = le32_to_cpu(cmd->fw_rev);  		priv->hw_rev = cmd->hw_rev; -		mwl8k_setup_2ghz_band(hw); +		mwl8k_set_caps(hw, le32_to_cpu(cmd->caps));  		priv->ap_macids_supported = 0x000000ff;  		priv->sta_macids_supported = 0x00000000; - -		off = le32_to_cpu(cmd->wcbbase0) & 0xffff; -		iowrite32(priv->txq[0].txd_dma, priv->sram + off); - +		priv->num_ampdu_queues = le32_to_cpu(cmd->num_of_ampdu_queues); +		if (priv->num_ampdu_queues > MWL8K_MAX_AMPDU_QUEUES) { +			wiphy_warn(hw->wiphy, "fw reported %d ampdu queues" +				   " but we only support %d.\n", +				   priv->num_ampdu_queues, +				   MWL8K_MAX_AMPDU_QUEUES); +			priv->num_ampdu_queues = MWL8K_MAX_AMPDU_QUEUES; +		}  		off = le32_to_cpu(cmd->rxwrptr) & 0xffff;  		iowrite32(priv->rxq[0].rxd_dma, priv->sram + off);  		off = le32_to_cpu(cmd->rxrdptr) & 0xffff;  		iowrite32(priv->rxq[0].rxd_dma, priv->sram + off); -		off = le32_to_cpu(cmd->wcbbase1) & 0xffff; -		iowrite32(priv->txq[1].txd_dma, priv->sram + off); +		priv->txq_offset[0] = le32_to_cpu(cmd->wcbbase0) & 0xffff; +		priv->txq_offset[1] = le32_to_cpu(cmd->wcbbase1) & 0xffff; +		priv->txq_offset[2] = le32_to_cpu(cmd->wcbbase2) & 0xffff; +		priv->txq_offset[3] = le32_to_cpu(cmd->wcbbase3) & 0xffff; -		off = le32_to_cpu(cmd->wcbbase2) & 0xffff; -		iowrite32(priv->txq[2].txd_dma, priv->sram + off); - -		off = le32_to_cpu(cmd->wcbbase3) & 0xffff; -		iowrite32(priv->txq[3].txd_dma, priv->sram + off); +		for (i = 0; i < priv->num_ampdu_queues; i++) +			priv->txq_offset[i + MWL8K_TX_WMM_QUEUES] = +				le32_to_cpu(cmd->wcbbase_ampdu[i]) & 0xffff;  	}  done: @@ -2098,12 +2459,20 @@ struct mwl8k_cmd_set_hw_spec {  	__le32 caps;  	__le32 rx_queue_ptr;  	__le32 num_tx_queues; -	__le32 tx_queue_ptrs[MWL8K_TX_QUEUES]; +	__le32 tx_queue_ptrs[MWL8K_MAX_TX_QUEUES];  	__le32 flags;  	__le32 num_tx_desc_per_queue;  	__le32 total_rxd;  } __packed; +/* If enabled, MWL8K_SET_HW_SPEC_FLAG_ENABLE_LIFE_TIME_EXPIRY will cause + * packets to expire 500 ms after the timestamp in the tx descriptor.  That is, + * the packets that are queued for more than 500ms, will be dropped in the + * hardware. This helps minimizing the issues caused due to head-of-line + * blocking where a slow client can hog the bandwidth and affect traffic to a + * faster client. + */ +#define MWL8K_SET_HW_SPEC_FLAG_ENABLE_LIFE_TIME_EXPIRY	0x00000400  #define MWL8K_SET_HW_SPEC_FLAG_HOST_DECR_MGMT		0x00000080  #define MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_PROBERESP	0x00000020  #define MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_BEACON		0x00000010 @@ -2124,7 +2493,7 @@ static int mwl8k_cmd_set_hw_spec(struct ieee80211_hw *hw)  	cmd->ps_cookie = cpu_to_le32(priv->cookie_dma);  	cmd->rx_queue_ptr = cpu_to_le32(priv->rxq[0].rxd_dma); -	cmd->num_tx_queues = cpu_to_le32(MWL8K_TX_QUEUES); +	cmd->num_tx_queues = cpu_to_le32(mwl8k_tx_queues(priv));  	/*  	 * Mac80211 stack has Q0 as highest priority and Q3 as lowest in @@ -2132,14 +2501,15 @@ static int mwl8k_cmd_set_hw_spec(struct ieee80211_hw *hw)  	 * in that order. Map Q3 of mac80211 to Q0 of firmware so that the  	 * priority is interpreted the right way in firmware.  	 */ -	for (i = 0; i < MWL8K_TX_QUEUES; i++) { -		int j = MWL8K_TX_QUEUES - 1 - i; +	for (i = 0; i < mwl8k_tx_queues(priv); i++) { +		int j = mwl8k_tx_queues(priv) - 1 - i;  		cmd->tx_queue_ptrs[i] = cpu_to_le32(priv->txq[j].txd_dma);  	}  	cmd->flags = cpu_to_le32(MWL8K_SET_HW_SPEC_FLAG_HOST_DECR_MGMT |  				 MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_PROBERESP | -				 MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_BEACON); +				 MWL8K_SET_HW_SPEC_FLAG_HOSTFORM_BEACON | +				 MWL8K_SET_HW_SPEC_FLAG_ENABLE_LIFE_TIME_EXPIRY);  	cmd->num_tx_desc_per_queue = cpu_to_le32(MWL8K_TX_DESCS);  	cmd->total_rxd = cpu_to_le32(MWL8K_RX_DESCS); @@ -2356,7 +2726,7 @@ struct mwl8k_cmd_tx_power {  	__le16 bw;  	__le16 sub_ch;  	__le16 power_level_list[MWL8K_TX_POWER_LEVEL_TOTAL]; -} __attribute__((packed)); +} __packed;  static int mwl8k_cmd_tx_power(struct ieee80211_hw *hw,  				     struct ieee80211_conf *conf, @@ -3123,6 +3493,65 @@ static int mwl8k_cmd_set_rateadapt_mode(struct ieee80211_hw *hw, __u16 mode)  }  /* + * CMD_GET_WATCHDOG_BITMAP. + */ +struct mwl8k_cmd_get_watchdog_bitmap { +	struct mwl8k_cmd_pkt header; +	u8	bitmap; +} __packed; + +static int mwl8k_cmd_get_watchdog_bitmap(struct ieee80211_hw *hw, u8 *bitmap) +{ +	struct mwl8k_cmd_get_watchdog_bitmap *cmd; +	int rc; + +	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); +	if (cmd == NULL) +		return -ENOMEM; + +	cmd->header.code = cpu_to_le16(MWL8K_CMD_GET_WATCHDOG_BITMAP); +	cmd->header.length = cpu_to_le16(sizeof(*cmd)); + +	rc = mwl8k_post_cmd(hw, &cmd->header); +	if (!rc) +		*bitmap = cmd->bitmap; + +	kfree(cmd); + +	return rc; +} + +#define INVALID_BA	0xAA +static void mwl8k_watchdog_ba_events(struct work_struct *work) +{ +	int rc; +	u8 bitmap = 0, stream_index; +	struct mwl8k_ampdu_stream *streams; +	struct mwl8k_priv *priv = +		container_of(work, struct mwl8k_priv, watchdog_ba_handle); + +	rc = mwl8k_cmd_get_watchdog_bitmap(priv->hw, &bitmap); +	if (rc) +		return; + +	if (bitmap == INVALID_BA) +		return; + +	/* the bitmap is the hw queue number.  Map it to the ampdu queue. */ +	stream_index = bitmap - MWL8K_TX_WMM_QUEUES; + +	BUG_ON(stream_index >= priv->num_ampdu_queues); + +	streams = &priv->ampdu[stream_index]; + +	if (streams->state == AMPDU_STREAM_ACTIVE) +		ieee80211_stop_tx_ba_session(streams->sta, streams->tid); + +	return; +} + + +/*   * CMD_BSS_START.   */  struct mwl8k_cmd_bss_start { @@ -3151,6 +3580,152 @@ static int mwl8k_cmd_bss_start(struct ieee80211_hw *hw,  }  /* + * CMD_BASTREAM. + */ + +/* + * UPSTREAM is tx direction + */ +#define BASTREAM_FLAG_DIRECTION_UPSTREAM	0x00 +#define BASTREAM_FLAG_IMMEDIATE_TYPE		0x01 + +enum ba_stream_action_type { +	MWL8K_BA_CREATE, +	MWL8K_BA_UPDATE, +	MWL8K_BA_DESTROY, +	MWL8K_BA_FLUSH, +	MWL8K_BA_CHECK, +}; + + +struct mwl8k_create_ba_stream { +	__le32	flags; +	__le32	idle_thrs; +	__le32	bar_thrs; +	__le32	window_size; +	u8	peer_mac_addr[6]; +	u8	dialog_token; +	u8	tid; +	u8	queue_id; +	u8	param_info; +	__le32	ba_context; +	u8	reset_seq_no_flag; +	__le16	curr_seq_no; +	u8	sta_src_mac_addr[6]; +} __packed; + +struct mwl8k_destroy_ba_stream { +	__le32	flags; +	__le32	ba_context; +} __packed; + +struct mwl8k_cmd_bastream { +	struct mwl8k_cmd_pkt	header; +	__le32	action; +	union { +		struct mwl8k_create_ba_stream	create_params; +		struct mwl8k_destroy_ba_stream	destroy_params; +	}; +} __packed; + +static int +mwl8k_check_ba(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream) +{ +	struct mwl8k_cmd_bastream *cmd; +	int rc; + +	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); +	if (cmd == NULL) +		return -ENOMEM; + +	cmd->header.code = cpu_to_le16(MWL8K_CMD_BASTREAM); +	cmd->header.length = cpu_to_le16(sizeof(*cmd)); + +	cmd->action = cpu_to_le32(MWL8K_BA_CHECK); + +	cmd->create_params.queue_id = stream->idx; +	memcpy(&cmd->create_params.peer_mac_addr[0], stream->sta->addr, +	       ETH_ALEN); +	cmd->create_params.tid = stream->tid; + +	cmd->create_params.flags = +		cpu_to_le32(BASTREAM_FLAG_IMMEDIATE_TYPE) | +		cpu_to_le32(BASTREAM_FLAG_DIRECTION_UPSTREAM); + +	rc = mwl8k_post_cmd(hw, &cmd->header); + +	kfree(cmd); + +	return rc; +} + +static int +mwl8k_create_ba(struct ieee80211_hw *hw, struct mwl8k_ampdu_stream *stream, +		u8 buf_size) +{ +	struct mwl8k_cmd_bastream *cmd; +	int rc; + +	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); +	if (cmd == NULL) +		return -ENOMEM; + + +	cmd->header.code = cpu_to_le16(MWL8K_CMD_BASTREAM); +	cmd->header.length = cpu_to_le16(sizeof(*cmd)); + +	cmd->action = cpu_to_le32(MWL8K_BA_CREATE); + +	cmd->create_params.bar_thrs = cpu_to_le32((u32)buf_size); +	cmd->create_params.window_size = cpu_to_le32((u32)buf_size); +	cmd->create_params.queue_id = stream->idx; + +	memcpy(cmd->create_params.peer_mac_addr, stream->sta->addr, ETH_ALEN); +	cmd->create_params.tid = stream->tid; +	cmd->create_params.curr_seq_no = cpu_to_le16(0); +	cmd->create_params.reset_seq_no_flag = 1; + +	cmd->create_params.param_info = +		(stream->sta->ht_cap.ampdu_factor & +		 IEEE80211_HT_AMPDU_PARM_FACTOR) | +		((stream->sta->ht_cap.ampdu_density << 2) & +		 IEEE80211_HT_AMPDU_PARM_DENSITY); + +	cmd->create_params.flags = +		cpu_to_le32(BASTREAM_FLAG_IMMEDIATE_TYPE | +					BASTREAM_FLAG_DIRECTION_UPSTREAM); + +	rc = mwl8k_post_cmd(hw, &cmd->header); + +	wiphy_debug(hw->wiphy, "Created a BA stream for %pM : tid %d\n", +		stream->sta->addr, stream->tid); +	kfree(cmd); + +	return rc; +} + +static void mwl8k_destroy_ba(struct ieee80211_hw *hw, +			     struct mwl8k_ampdu_stream *stream) +{ +	struct mwl8k_cmd_bastream *cmd; + +	cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); +	if (cmd == NULL) +		return; + +	cmd->header.code = cpu_to_le16(MWL8K_CMD_BASTREAM); +	cmd->header.length = cpu_to_le16(sizeof(*cmd)); +	cmd->action = cpu_to_le32(MWL8K_BA_DESTROY); + +	cmd->destroy_params.ba_context = cpu_to_le32(stream->idx); +	mwl8k_post_cmd(hw, &cmd->header); + +	wiphy_debug(hw->wiphy, "Deleted BA stream index %d\n", stream->idx); + +	kfree(cmd); +} + +/*   * CMD_SET_NEW_STN.   */  struct mwl8k_cmd_set_new_stn { @@ -3274,7 +3849,7 @@ struct mwl8k_cmd_update_encryption {  	__u8 mac_addr[6];  	__u8 encr_type; -} __attribute__((packed)); +} __packed;  struct mwl8k_cmd_set_key {  	struct mwl8k_cmd_pkt header; @@ -3294,7 +3869,7 @@ struct mwl8k_cmd_set_key {  	__le16 tkip_tsc_low;  	__le32 tkip_tsc_high;  	__u8 mac_addr[6]; -} __attribute__((packed)); +} __packed;  enum {  	MWL8K_ENCR_ENABLE, @@ -3671,6 +4246,11 @@ static irqreturn_t mwl8k_interrupt(int irq, void *dev_id)  		tasklet_schedule(&priv->poll_rx_task);  	} +	if (status & MWL8K_A2H_INT_BA_WATCHDOG) { +		status &= ~MWL8K_A2H_INT_BA_WATCHDOG; +		ieee80211_queue_work(hw, &priv->watchdog_ba_handle); +	} +  	if (status)  		iowrite32(~status, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS); @@ -3699,7 +4279,7 @@ static void mwl8k_tx_poll(unsigned long data)  	spin_lock_bh(&priv->tx_lock); -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		limit -= mwl8k_txq_reclaim(hw, i, limit, 0);  	if (!priv->pending_tx_pkts && priv->tx_wait != NULL) { @@ -3774,6 +4354,8 @@ static int mwl8k_start(struct ieee80211_hw *hw)  	/* Enable interrupts */  	iowrite32(MWL8K_A2H_EVENTS, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); +	iowrite32(MWL8K_A2H_EVENTS, +		  priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK);  	rc = mwl8k_fw_lock(hw);  	if (!rc) { @@ -3829,6 +4411,7 @@ static void mwl8k_stop(struct ieee80211_hw *hw)  	/* Stop finalize join worker */  	cancel_work_sync(&priv->finalize_join_worker); +	cancel_work_sync(&priv->watchdog_ba_handle);  	if (priv->beacon_skb != NULL)  		dev_kfree_skb(priv->beacon_skb); @@ -3837,7 +4420,7 @@ static void mwl8k_stop(struct ieee80211_hw *hw)  	tasklet_disable(&priv->poll_rx_task);  	/* Return all skbs to mac80211 */ -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		mwl8k_txq_reclaim(hw, i, INT_MAX, 1);  } @@ -3958,9 +4541,12 @@ static int mwl8k_config(struct ieee80211_hw *hw, u32 changed)  		conf->power_level = 18;  	if (priv->ap_fw) { -		rc = mwl8k_cmd_tx_power(hw, conf, conf->power_level); -		if (rc) -			goto out; + +		if (conf->flags & IEEE80211_CONF_CHANGE_POWER) { +			rc = mwl8k_cmd_tx_power(hw, conf, conf->power_level); +			if (rc) +				goto out; +		}  		rc = mwl8k_cmd_rf_antenna(hw, MWL8K_RF_ANTENNA_RX, 0x3);  		if (rc) @@ -3987,7 +4573,7 @@ mwl8k_bss_info_changed_sta(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  			   struct ieee80211_bss_conf *info, u32 changed)  {  	struct mwl8k_priv *priv = hw->priv; -	u32 ap_legacy_rates; +	u32 ap_legacy_rates = 0;  	u8 ap_mcs_rates[16];  	int rc; @@ -4312,6 +4898,8 @@ static int mwl8k_sta_add(struct ieee80211_hw *hw,  		ret = mwl8k_cmd_update_stadb_add(hw, vif, sta);  		if (ret >= 0) {  			MWL8K_STA(sta)->peer_id = ret; +			if (sta->ht_cap.ht_supported) +				MWL8K_STA(sta)->is_ampdu_allowed = true;  			ret = 0;  		} @@ -4335,14 +4923,14 @@ static int mwl8k_conf_tx(struct ieee80211_hw *hw, u16 queue,  	rc = mwl8k_fw_lock(hw);  	if (!rc) { -		BUG_ON(queue > MWL8K_TX_QUEUES - 1); +		BUG_ON(queue > MWL8K_TX_WMM_QUEUES - 1);  		memcpy(&priv->wmm_params[queue], params, sizeof(*params));  		if (!priv->wmm_enabled)  			rc = mwl8k_cmd_set_wmm_mode(hw, 1);  		if (!rc) { -			int q = MWL8K_TX_QUEUES - 1 - queue; +			int q = MWL8K_TX_WMM_QUEUES - 1 - queue;  			rc = mwl8k_cmd_set_edca_params(hw, q,  						       params->cw_min,  						       params->cw_max, @@ -4378,21 +4966,118 @@ static int mwl8k_get_survey(struct ieee80211_hw *hw, int idx,  	return 0;  } +#define MAX_AMPDU_ATTEMPTS 5 +  static int  mwl8k_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,  		   enum ieee80211_ampdu_mlme_action action,  		   struct ieee80211_sta *sta, u16 tid, u16 *ssn,  		   u8 buf_size)  { + +	int i, rc = 0; +	struct mwl8k_priv *priv = hw->priv; +	struct mwl8k_ampdu_stream *stream; +	u8 *addr = sta->addr; + +	if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION)) +		return -ENOTSUPP; + +	spin_lock(&priv->stream_lock); +	stream = mwl8k_lookup_stream(hw, addr, tid); +  	switch (action) {  	case IEEE80211_AMPDU_RX_START:  	case IEEE80211_AMPDU_RX_STOP: -		if (!(hw->flags & IEEE80211_HW_AMPDU_AGGREGATION)) -			return -ENOTSUPP; -		return 0; +		break; +	case IEEE80211_AMPDU_TX_START: +		/* By the time we get here the hw queues may contain outgoing +		 * packets for this RA/TID that are not part of this BA +		 * session.  The hw will assign sequence numbers to these +		 * packets as they go out.  So if we query the hw for its next +		 * sequence number and use that for the SSN here, it may end up +		 * being wrong, which will lead to sequence number mismatch at +		 * the recipient.  To avoid this, we reset the sequence number +		 * to O for the first MPDU in this BA stream. +		 */ +		*ssn = 0; +		if (stream == NULL) { +			/* This means that somebody outside this driver called +			 * ieee80211_start_tx_ba_session.  This is unexpected +			 * because we do our own rate control.  Just warn and +			 * move on. +			 */ +			wiphy_warn(hw->wiphy, "Unexpected call to %s.  " +				   "Proceeding anyway.\n", __func__); +			stream = mwl8k_add_stream(hw, sta, tid); +		} +		if (stream == NULL) { +			wiphy_debug(hw->wiphy, "no free AMPDU streams\n"); +			rc = -EBUSY; +			break; +		} +		stream->state = AMPDU_STREAM_IN_PROGRESS; + +		/* Release the lock before we do the time consuming stuff */ +		spin_unlock(&priv->stream_lock); +		for (i = 0; i < MAX_AMPDU_ATTEMPTS; i++) { +			rc = mwl8k_check_ba(hw, stream); + +			if (!rc) +				break; +			/* +			 * HW queues take time to be flushed, give them +			 * sufficient time +			 */ + +			msleep(1000); +		} +		spin_lock(&priv->stream_lock); +		if (rc) { +			wiphy_err(hw->wiphy, "Stream for tid %d busy after %d" +				" attempts\n", tid, MAX_AMPDU_ATTEMPTS); +			mwl8k_remove_stream(hw, stream); +			rc = -EBUSY; +			break; +		} +		ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); +		break; +	case IEEE80211_AMPDU_TX_STOP: +		if (stream == NULL) +			break; +		if (stream->state == AMPDU_STREAM_ACTIVE) { +			spin_unlock(&priv->stream_lock); +			mwl8k_destroy_ba(hw, stream); +			spin_lock(&priv->stream_lock); +		} +		mwl8k_remove_stream(hw, stream); +		ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid); +		break; +	case IEEE80211_AMPDU_TX_OPERATIONAL: +		BUG_ON(stream == NULL); +		BUG_ON(stream->state != AMPDU_STREAM_IN_PROGRESS); +		spin_unlock(&priv->stream_lock); +		rc = mwl8k_create_ba(hw, stream, buf_size); +		spin_lock(&priv->stream_lock); +		if (!rc) +			stream->state = AMPDU_STREAM_ACTIVE; +		else { +			spin_unlock(&priv->stream_lock); +			mwl8k_destroy_ba(hw, stream); +			spin_lock(&priv->stream_lock); +			wiphy_debug(hw->wiphy, +				"Failed adding stream for sta %pM tid %d\n", +				addr, tid); +			mwl8k_remove_stream(hw, stream); +		} +		break; +  	default: -		return -ENOTSUPP; +		rc = -ENOTSUPP;  	} + +	spin_unlock(&priv->stream_lock); +	return rc;  }  static const struct ieee80211_ops mwl8k_ops = { @@ -4441,7 +5126,7 @@ enum {  	MWL8366,  }; -#define MWL8K_8366_AP_FW_API 1 +#define MWL8K_8366_AP_FW_API 2  #define _MWL8K_8366_AP_FW(api) "mwl8k/fmimage_8366_ap-" #api ".fw"  #define MWL8K_8366_AP_FW(api) _MWL8K_8366_AP_FW(api) @@ -4607,6 +5292,23 @@ static int mwl8k_init_firmware(struct ieee80211_hw *hw, char *fw_image,  	return rc;  } +static int mwl8k_init_txqs(struct ieee80211_hw *hw) +{ +	struct mwl8k_priv *priv = hw->priv; +	int rc = 0; +	int i; + +	for (i = 0; i < mwl8k_tx_queues(priv); i++) { +		rc = mwl8k_txq_init(hw, i); +		if (rc) +			break; +		if (priv->ap_fw) +			iowrite32(priv->txq[i].txd_dma, +				  priv->sram + priv->txq_offset[i]); +	} +	return rc; +} +  /* initialize hw after successfully loading a firmware image */  static int mwl8k_probe_hw(struct ieee80211_hw *hw)  { @@ -4634,17 +5336,26 @@ static int mwl8k_probe_hw(struct ieee80211_hw *hw)  		goto err_stop_firmware;  	rxq_refill(hw, 0, INT_MAX); -	for (i = 0; i < MWL8K_TX_QUEUES; i++) { -		rc = mwl8k_txq_init(hw, i); +	/* For the sta firmware, we need to know the dma addresses of tx queues +	 * before sending MWL8K_CMD_GET_HW_SPEC.  So we must initialize them +	 * prior to issuing this command.  But for the AP case, we learn the +	 * total number of queues from the result CMD_GET_HW_SPEC, so for this +	 * case we must initialize the tx queues after. +	 */ +	priv->num_ampdu_queues = 0; +	if (!priv->ap_fw) { +		rc = mwl8k_init_txqs(hw);  		if (rc)  			goto err_free_queues;  	}  	iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS);  	iowrite32(0, priv->regs + MWL8K_HIU_A2H_INTERRUPT_MASK); -	iowrite32(MWL8K_A2H_INT_TX_DONE | MWL8K_A2H_INT_RX_READY, +	iowrite32(MWL8K_A2H_INT_TX_DONE|MWL8K_A2H_INT_RX_READY| +		  MWL8K_A2H_INT_BA_WATCHDOG,  		  priv->regs + MWL8K_HIU_A2H_INTERRUPT_CLEAR_SEL); -	iowrite32(0xffffffff, priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK); +	iowrite32(MWL8K_A2H_INT_OPC_DONE, +		  priv->regs + MWL8K_HIU_A2H_INTERRUPT_STATUS_MASK);  	rc = request_irq(priv->pdev->irq, mwl8k_interrupt,  			 IRQF_SHARED, MWL8K_NAME, hw); @@ -4653,6 +5364,8 @@ static int mwl8k_probe_hw(struct ieee80211_hw *hw)  		goto err_free_queues;  	} +	memset(priv->ampdu, 0, sizeof(priv->ampdu)); +  	/*  	 * Temporarily enable interrupts.  Initial firmware host  	 * commands use interrupts and avoid polling.  Disable @@ -4664,6 +5377,8 @@ static int mwl8k_probe_hw(struct ieee80211_hw *hw)  	if (priv->ap_fw) {  		rc = mwl8k_cmd_get_hw_spec_ap(hw);  		if (!rc) +			rc = mwl8k_init_txqs(hw); +		if (!rc)  			rc = mwl8k_cmd_set_hw_spec(hw);  	} else {  		rc = mwl8k_cmd_get_hw_spec_sta(hw); @@ -4705,7 +5420,7 @@ err_free_irq:  	free_irq(priv->pdev->irq, hw);  err_free_queues: -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		mwl8k_txq_deinit(hw, i);  	mwl8k_rxq_deinit(hw, 0); @@ -4727,7 +5442,7 @@ static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image)  	mwl8k_stop(hw);  	mwl8k_rxq_deinit(hw, 0); -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		mwl8k_txq_deinit(hw, i);  	rc = mwl8k_init_firmware(hw, fw_image, false); @@ -4746,7 +5461,7 @@ static int mwl8k_reload_firmware(struct ieee80211_hw *hw, char *fw_image)  	if (rc)  		goto fail; -	for (i = 0; i < MWL8K_TX_QUEUES; i++) { +	for (i = 0; i < MWL8K_TX_WMM_QUEUES; i++) {  		rc = mwl8k_conf_tx(hw, i, &priv->wmm_params[i]);  		if (rc)  			goto fail; @@ -4778,9 +5493,11 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv)  	hw->extra_tx_headroom =  		sizeof(struct mwl8k_dma_data) - sizeof(struct ieee80211_cts); +	hw->extra_tx_headroom -= priv->ap_fw ? REDUCED_TX_HEADROOM : 0; +  	hw->channel_change_time = 10; -	hw->queues = MWL8K_TX_QUEUES; +	hw->queues = MWL8K_TX_WMM_QUEUES;  	/* Set rssi values to dBm */  	hw->flags |= IEEE80211_HW_SIGNAL_DBM | IEEE80211_HW_HAS_RATE_CONTROL; @@ -4796,6 +5513,8 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv)  	/* Finalize join worker */  	INIT_WORK(&priv->finalize_join_worker, mwl8k_finalize_join_worker); +	/* Handle watchdog ba events */ +	INIT_WORK(&priv->watchdog_ba_handle, mwl8k_watchdog_ba_events);  	/* TX reclaim and RX tasklets.  */  	tasklet_init(&priv->poll_tx_task, mwl8k_tx_poll, (unsigned long)hw); @@ -4815,6 +5534,8 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv)  	spin_lock_init(&priv->tx_lock); +	spin_lock_init(&priv->stream_lock); +  	priv->tx_wait = NULL;  	rc = mwl8k_probe_hw(hw); @@ -4836,7 +5557,7 @@ static int mwl8k_firmware_load_success(struct mwl8k_priv *priv)  	return 0;  err_unprobe_hw: -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		mwl8k_txq_deinit(hw, i);  	mwl8k_rxq_deinit(hw, 0); @@ -4995,10 +5716,10 @@ static void __devexit mwl8k_remove(struct pci_dev *pdev)  	mwl8k_hw_reset(priv);  	/* Return all skbs to mac80211 */ -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		mwl8k_txq_reclaim(hw, i, INT_MAX, 1); -	for (i = 0; i < MWL8K_TX_QUEUES; i++) +	for (i = 0; i < mwl8k_tx_queues(priv); i++)  		mwl8k_txq_deinit(hw, i);  	mwl8k_rxq_deinit(hw, 0);  |