Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 27876a29 authored by Sujith Manoharan's avatar Sujith Manoharan Committed by John W. Linville
Browse files

ath9k_htc: Add support for TX completion



Now that the infrastructure is in place, process WMI
TX status events and complete packets.

Signed-off-by: default avatarSujith Manoharan <Sujith.Manoharan@atheros.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 84c9e164
Loading
Loading
Loading
Loading
+11 −3
Original line number Diff line number Diff line
@@ -266,11 +266,17 @@ struct ath9k_htc_rx {
#define ATH9K_HTC_TX_THRESHOLD (MAX_TX_BUF_NUM - ATH9K_HTC_TX_RESERVE)

#define ATH9K_HTC_OP_TX_QUEUES_STOP BIT(0)
#define ATH9K_HTC_OP_TX_DRAIN       BIT(1)

struct ath9k_htc_tx {
	u8 flags;
	int queued_cnt;
	struct sk_buff_head tx_queue;
	struct sk_buff_head mgmt_ep_queue;
	struct sk_buff_head cab_ep_queue;
	struct sk_buff_head data_be_queue;
	struct sk_buff_head data_bk_queue;
	struct sk_buff_head data_vi_queue;
	struct sk_buff_head data_vo_queue;
	struct sk_buff_head tx_failed;
	DECLARE_BITMAP(tx_slot, MAX_TX_BUF_NUM);
	spinlock_t tx_lock;
@@ -465,8 +471,8 @@ struct ath9k_htc_priv {

	struct tasklet_struct swba_tasklet;
	struct tasklet_struct rx_tasklet;
	struct tasklet_struct tx_tasklet;
	struct delayed_work ani_work;
	struct tasklet_struct tx_failed_tasklet;
	struct work_struct ps_work;
	struct work_struct fatal_work;

@@ -533,7 +539,6 @@ void ath9k_htc_start_ani(struct ath9k_htc_priv *priv);
void ath9k_htc_stop_ani(struct ath9k_htc_priv *priv);

int ath9k_tx_init(struct ath9k_htc_priv *priv);
void ath9k_tx_tasklet(unsigned long data);
int ath9k_htc_tx_start(struct ath9k_htc_priv *priv,
		       struct sk_buff *skb, u8 slot, bool is_cab);
void ath9k_tx_cleanup(struct ath9k_htc_priv *priv);
@@ -547,6 +552,9 @@ void ath9k_htc_check_wake_queues(struct ath9k_htc_priv *priv);
int ath9k_htc_tx_get_slot(struct ath9k_htc_priv *priv);
void ath9k_htc_tx_clear_slot(struct ath9k_htc_priv *priv, int slot);
void ath9k_htc_tx_drain(struct ath9k_htc_priv *priv);
void ath9k_htc_txstatus(struct ath9k_htc_priv *priv, void *wmi_event);
void ath9k_htc_tx_failed(struct ath9k_htc_priv *priv);
void ath9k_tx_failed_tasklet(unsigned long data);

int ath9k_rx_init(struct ath9k_htc_priv *priv);
void ath9k_rx_cleanup(struct ath9k_htc_priv *priv);
+1 −1
Original line number Diff line number Diff line
@@ -678,7 +678,7 @@ static int ath9k_init_priv(struct ath9k_htc_priv *priv,
	mutex_init(&priv->htc_pm_lock);
	tasklet_init(&priv->rx_tasklet, ath9k_rx_tasklet,
		     (unsigned long)priv);
	tasklet_init(&priv->tx_tasklet, ath9k_tx_tasklet,
	tasklet_init(&priv->tx_failed_tasklet, ath9k_tx_failed_tasklet,
		     (unsigned long)priv);
	INIT_DELAYED_WORK(&priv->ani_work, ath9k_htc_ani_work);
	INIT_WORK(&priv->ps_work, ath9k_ps_work);
+183 −25
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ void ath9k_htc_tx_clear_slot(struct ath9k_htc_priv *priv, int slot)
	spin_unlock_bh(&priv->tx.tx_lock);
}

static enum htc_endpoint_id get_htc_epid(struct ath9k_htc_priv *priv,
static inline enum htc_endpoint_id get_htc_epid(struct ath9k_htc_priv *priv,
						u16 qnum)
{
	enum htc_endpoint_id epid;
@@ -127,6 +127,30 @@ static enum htc_endpoint_id get_htc_epid(struct ath9k_htc_priv *priv,
	return epid;
}

static inline struct sk_buff_head*
get_htc_epid_queue(struct ath9k_htc_priv *priv, u8 epid)
{
	struct ath_common *common = ath9k_hw_common(priv->ah);
	struct sk_buff_head *epid_queue = NULL;

	if (epid == priv->mgmt_ep)
		epid_queue = &priv->tx.mgmt_ep_queue;
	else if (epid == priv->cab_ep)
		epid_queue = &priv->tx.cab_ep_queue;
	else if (epid == priv->data_be_ep)
		epid_queue = &priv->tx.data_be_queue;
	else if (epid == priv->data_bk_ep)
		epid_queue = &priv->tx.data_bk_queue;
	else if (epid == priv->data_vi_ep)
		epid_queue = &priv->tx.data_vi_queue;
	else if (epid == priv->data_vo_ep)
		epid_queue = &priv->tx.data_vo_queue;
	else
		ath_err(common, "Invalid EPID: %d\n", epid);

	return epid_queue;
}

/*
 * Removes the driver header and returns the TX slot number
 */
@@ -387,11 +411,15 @@ static void ath9k_htc_check_tx_aggr(struct ath9k_htc_priv *priv,
}

static void ath9k_htc_tx_process(struct ath9k_htc_priv *priv,
				 struct sk_buff *skb)
				 struct sk_buff *skb,
				 struct __wmi_event_txstatus *txs)
{
	struct ieee80211_vif *vif;
	struct ath9k_htc_tx_ctl *tx_ctl;
	struct ieee80211_tx_info *tx_info;
	struct ieee80211_tx_rate *rate;
	struct ieee80211_conf *cur_conf = &priv->hw->conf;
	struct ieee80211_supported_band *sband;
	bool txok;
	int slot;

@@ -405,6 +433,8 @@ static void ath9k_htc_tx_process(struct ath9k_htc_priv *priv,
	txok = tx_ctl->txok;
	tx_info = IEEE80211_SKB_CB(skb);
	vif = tx_info->control.vif;
	rate = &tx_info->status.rates[0];
	sband = priv->hw->wiphy->bands[cur_conf->channel->band];

	memset(&tx_info->status, 0, sizeof(tx_info->status));

@@ -412,11 +442,33 @@ static void ath9k_htc_tx_process(struct ath9k_htc_priv *priv,
	 * URB submission failed for this frame, it never reached
	 * the target.
	 */
	if (!txok || !vif)
	if (!txok || !vif || !txs)
		goto send_mac80211;

	if (txs->ts_flags & ATH9K_HTC_TXSTAT_ACK)
		tx_info->flags |= IEEE80211_TX_STAT_ACK;

	if (txs->ts_flags & ATH9K_HTC_TXSTAT_FILT)
		tx_info->flags |= IEEE80211_TX_STAT_TX_FILTERED;

	if (txs->ts_flags & ATH9K_HTC_TXSTAT_RTC_CTS)
		rate->flags |= IEEE80211_TX_RC_USE_RTS_CTS;

	rate->count = 1;
	rate->idx = MS(txs->ts_rate, ATH9K_HTC_TXSTAT_RATE);

	if (txs->ts_flags & ATH9K_HTC_TXSTAT_MCS) {
		rate->flags |= IEEE80211_TX_RC_MCS;

		if (txs->ts_flags & ATH9K_HTC_TXSTAT_CW40)
			rate->flags |= IEEE80211_TX_RC_40_MHZ_WIDTH;
		if (txs->ts_flags & ATH9K_HTC_TXSTAT_SGI)
			rate->flags |= IEEE80211_TX_RC_SHORT_GI;
	} else {
		if (cur_conf->channel->band == IEEE80211_BAND_5GHZ)
			rate->idx += 4; /* No CCK rates */
	}

	ath9k_htc_check_tx_aggr(priv, vif, skb);

send_mac80211:
@@ -431,37 +483,130 @@ static void ath9k_htc_tx_process(struct ath9k_htc_priv *priv,
	ieee80211_tx_status(priv->hw, skb);
}

static inline void ath9k_htc_tx_drainq(struct ath9k_htc_priv *priv,
				       struct sk_buff_head *queue)
{
	struct sk_buff *skb;

	while ((skb = skb_dequeue(queue)) != NULL) {
		ath9k_htc_tx_process(priv, skb, NULL);
	}
}

void ath9k_htc_tx_drain(struct ath9k_htc_priv *priv)
{
	struct sk_buff *skb = NULL;
	spin_lock_bh(&priv->tx.tx_lock);
	priv->tx.flags |= ATH9K_HTC_OP_TX_DRAIN;
	spin_unlock_bh(&priv->tx.tx_lock);

	/*
	 * Ensure that all pending TX frames are flushed,
	 * and that the TX completion tasklet is killed.
	 * and that the TX completion/failed tasklets is killed.
	 */
	htc_stop(priv->htc);
	tasklet_kill(&priv->tx_tasklet);
	tasklet_kill(&priv->wmi->wmi_event_tasklet);
	tasklet_kill(&priv->tx_failed_tasklet);

	while ((skb = skb_dequeue(&priv->tx.tx_queue)) != NULL) {
		ath9k_htc_tx_process(priv, skb);
	ath9k_htc_tx_drainq(priv, &priv->tx.mgmt_ep_queue);
	ath9k_htc_tx_drainq(priv, &priv->tx.cab_ep_queue);
	ath9k_htc_tx_drainq(priv, &priv->tx.data_be_queue);
	ath9k_htc_tx_drainq(priv, &priv->tx.data_bk_queue);
	ath9k_htc_tx_drainq(priv, &priv->tx.data_vi_queue);
	ath9k_htc_tx_drainq(priv, &priv->tx.data_vo_queue);
	ath9k_htc_tx_drainq(priv, &priv->tx.tx_failed);

	spin_lock_bh(&priv->tx.tx_lock);
	priv->tx.flags &= ~ATH9K_HTC_OP_TX_DRAIN;
	spin_unlock_bh(&priv->tx.tx_lock);
}

	while ((skb = skb_dequeue(&priv->tx.tx_failed)) != NULL) {
		ath9k_htc_tx_process(priv, skb);
void ath9k_tx_failed_tasklet(unsigned long data)
{
	struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;

	spin_lock_bh(&priv->tx.tx_lock);
	if (priv->tx.flags & ATH9K_HTC_OP_TX_DRAIN) {
		spin_unlock_bh(&priv->tx.tx_lock);
		return;
	}
	spin_unlock_bh(&priv->tx.tx_lock);

	ath9k_htc_tx_drainq(priv, &priv->tx.tx_failed);
}

void ath9k_tx_tasklet(unsigned long data)
static inline bool check_cookie(struct ath9k_htc_priv *priv,
				struct sk_buff *skb,
				u8 cookie, u8 epid)
{
	struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data;
	struct sk_buff *skb = NULL;
	u8 fcookie = 0;

	if (epid == priv->mgmt_ep) {
		struct tx_mgmt_hdr *hdr;
		hdr = (struct tx_mgmt_hdr *) skb->data;
		fcookie = hdr->cookie;
	} else if ((epid == priv->data_bk_ep) ||
		   (epid == priv->data_be_ep) ||
		   (epid == priv->data_vi_ep) ||
		   (epid == priv->data_vo_ep) ||
		   (epid == priv->cab_ep)) {
		struct tx_frame_hdr *hdr;
		hdr = (struct tx_frame_hdr *) skb->data;
		fcookie = hdr->cookie;
	}

	if (fcookie == cookie)
		return true;

	return false;
}

static struct sk_buff* ath9k_htc_tx_get_packet(struct ath9k_htc_priv *priv,
					       struct __wmi_event_txstatus *txs)
{
	struct ath_common *common = ath9k_hw_common(priv->ah);
	struct sk_buff_head *epid_queue;
	struct sk_buff *skb, *tmp;
	unsigned long flags;
	u8 epid = MS(txs->ts_rate, ATH9K_HTC_TXSTAT_EPID);

	epid_queue = get_htc_epid_queue(priv, epid);
	if (!epid_queue)
		return NULL;

	while ((skb = skb_dequeue(&priv->tx.tx_queue)) != NULL) {
		ath9k_htc_tx_process(priv, skb);
	spin_lock_irqsave(&epid_queue->lock, flags);
	skb_queue_walk_safe(epid_queue, skb, tmp) {
		if (check_cookie(priv, skb, txs->cookie, epid)) {
			__skb_unlink(skb, epid_queue);
			spin_unlock_irqrestore(&epid_queue->lock, flags);
			return skb;
		}
	}
	spin_unlock_irqrestore(&epid_queue->lock, flags);

	ath_dbg(common, ATH_DBG_XMIT,
		"No matching packet for cookie: %d, epid: %d\n",
		txs->cookie, epid);

	while ((skb = skb_dequeue(&priv->tx.tx_failed)) != NULL) {
		ath9k_htc_tx_process(priv, skb);
	return NULL;
}

void ath9k_htc_txstatus(struct ath9k_htc_priv *priv, void *wmi_event)
{
	struct wmi_event_txstatus *txs = (struct wmi_event_txstatus *)wmi_event;
	struct __wmi_event_txstatus *__txs;
	struct sk_buff *skb;
	int i;

	for (i = 0; i < txs->cnt; i++) {
		WARN_ON(txs->cnt > HTC_MAX_TX_STATUS);

		__txs = &txs->txstatus[i];

		skb = ath9k_htc_tx_get_packet(priv, __txs);
		if (!skb)
			continue;

		ath9k_htc_tx_process(priv, skb, __txs);
	}

	/* Wake TX queues if needed */
@@ -473,21 +618,34 @@ void ath9k_htc_txep(void *drv_priv, struct sk_buff *skb,
{
	struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *) drv_priv;
	struct ath9k_htc_tx_ctl *tx_ctl;
	struct sk_buff_head *epid_queue;

	tx_ctl = HTC_SKB_CB(skb);
	tx_ctl->txok = txok;

	if (txok)
		skb_queue_tail(&priv->tx.tx_queue, skb);
	else
	if (!txok) {
		skb_queue_tail(&priv->tx.tx_failed, skb);
		tasklet_schedule(&priv->tx_failed_tasklet);
		return;
	}

	epid_queue = get_htc_epid_queue(priv, ep_id);
	if (!epid_queue) {
		dev_kfree_skb_any(skb);
		return;
	}

	tasklet_schedule(&priv->tx_tasklet);
	skb_queue_tail(epid_queue, skb);
}

int ath9k_tx_init(struct ath9k_htc_priv *priv)
{
	skb_queue_head_init(&priv->tx.tx_queue);
	skb_queue_head_init(&priv->tx.mgmt_ep_queue);
	skb_queue_head_init(&priv->tx.cab_ep_queue);
	skb_queue_head_init(&priv->tx.data_be_queue);
	skb_queue_head_init(&priv->tx.data_bk_queue);
	skb_queue_head_init(&priv->tx.data_vi_queue);
	skb_queue_head_init(&priv->tx.data_vo_queue);
	skb_queue_head_init(&priv->tx.tx_failed);
	return 0;
}
+10 −0
Original line number Diff line number Diff line
@@ -163,6 +163,16 @@ void ath9k_wmi_event_tasklet(unsigned long data)
			wmi->drv_priv->debug.txrate = be32_to_cpu(txrate);
#endif
			break;
		case WMI_TXSTATUS_EVENTID:
			spin_lock_bh(&priv->tx.tx_lock);
			if (priv->tx.flags & ATH9K_HTC_OP_TX_DRAIN) {
				spin_unlock_bh(&priv->tx.tx_lock);
				break;
			}
			spin_unlock_bh(&priv->tx.tx_lock);

			ath9k_htc_txstatus(priv, wmi_event);
			break;
		default:
			break;
		}
+2 −2
Original line number Diff line number Diff line
@@ -45,9 +45,9 @@ struct wmi_event_swba {
/*
 * 64 - HTC header - WMI header - 1 / txstatus
 * And some other hdr. space is also accounted for.
 * 13 seems to be the magic number.
 * 12 seems to be the magic number.
 */
#define HTC_MAX_TX_STATUS 13
#define HTC_MAX_TX_STATUS 12

#define ATH9K_HTC_TXSTAT_ACK        BIT(0)
#define ATH9K_HTC_TXSTAT_FILT       BIT(1)