Commit 464021ff authored by Emmanuel Grumbach's avatar Emmanuel Grumbach Committed by John W. Linville

iwlagn: move the check_empty logic to the transport layer

This logic is responsible to tell mac80211 when the HW queues are
empty and the BA session can be started / torn down.

Fix a bug on the way:
When the the Tx BA session is stopped and the HW queues aren't empty,
we stop the SW queue to drain the HW queue and then switch to the
legacy HW queue. This is the IWL_EMPTYING_HW_QUEUE_DELBA state.

While in this state, we never wake the SW queue, even when the HW
queue is almost empty, since we need to drain it completely. Look
at iwl_trans_pcie_reclaim regarding this.

Once the HW queue is really empty, we must wake the SW queue in order
to get traffic to the legacy queue.
This step was missing leading to an odd situation were the traffic
would just stall after we tore down a Tx BA session while the HW
queue was not empty.
Signed-off-by: default avatarEmmanuel Grumbach <emmanuel.grumbach@intel.com>
Signed-off-by: default avatarWey-Yi Guy <wey-yi.w.guy@intel.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent 288712a6
...@@ -477,43 +477,6 @@ int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif, ...@@ -477,43 +477,6 @@ int iwlagn_tx_agg_stop(struct iwl_priv *priv, struct ieee80211_vif *vif,
return 0; return 0;
} }
static int iwlagn_txq_check_empty(struct iwl_priv *priv,
int sta_id, u8 tid, int txq_id)
{
struct iwl_queue *q = &priv->txq[txq_id].q;
u8 *addr = priv->stations[sta_id].sta.sta.addr;
struct iwl_tid_data *tid_data = &priv->shrd->tid_data[sta_id][tid];
struct iwl_rxon_context *ctx;
ctx = &priv->contexts[priv->stations[sta_id].ctxid];
lockdep_assert_held(&priv->shrd->sta_lock);
switch (priv->shrd->tid_data[sta_id][tid].agg.state) {
case IWL_EMPTYING_HW_QUEUE_DELBA:
/* We are reclaiming the last packet of the */
/* aggregated HW queue */
if ((txq_id == tid_data->agg.txq_id) &&
(q->read_ptr == q->write_ptr)) {
IWL_DEBUG_HT(priv, "HW queue empty: continue DELBA flow\n");
iwl_trans_txq_agg_disable(trans(priv), txq_id);
tid_data->agg.state = IWL_AGG_OFF;
ieee80211_stop_tx_ba_cb_irqsafe(ctx->vif, addr, tid);
}
break;
case IWL_EMPTYING_HW_QUEUE_ADDBA:
/* We are reclaiming the last packet of the queue */
if (tid_data->tfds_in_queue == 0) {
IWL_DEBUG_HT(priv, "HW queue empty: continue ADDBA flow\n");
tid_data->agg.state = IWL_AGG_ON;
ieee80211_start_tx_ba_cb_irqsafe(ctx->vif, addr, tid);
}
break;
}
return 0;
}
static void iwlagn_non_agg_tx_status(struct iwl_priv *priv, static void iwlagn_non_agg_tx_status(struct iwl_priv *priv,
struct iwl_rxon_context *ctx, struct iwl_rxon_context *ctx,
const u8 *addr1) const u8 *addr1)
...@@ -724,21 +687,6 @@ static inline u32 iwlagn_get_scd_ssn(struct iwlagn_tx_resp *tx_resp) ...@@ -724,21 +687,6 @@ static inline u32 iwlagn_get_scd_ssn(struct iwlagn_tx_resp *tx_resp)
tx_resp->frame_count) & MAX_SN; tx_resp->frame_count) & MAX_SN;
} }
static void iwl_free_tfds_in_queue(struct iwl_priv *priv,
int sta_id, int tid, int freed)
{
lockdep_assert_held(&priv->shrd->sta_lock);
if (priv->shrd->tid_data[sta_id][tid].tfds_in_queue >= freed)
priv->shrd->tid_data[sta_id][tid].tfds_in_queue -= freed;
else {
IWL_DEBUG_TX(priv, "free more than tfds_in_queue (%u:%d)\n",
priv->shrd->tid_data[sta_id][tid].tfds_in_queue,
freed);
priv->shrd->tid_data[sta_id][tid].tfds_in_queue = 0;
}
}
static void iwlagn_count_tx_err_status(struct iwl_priv *priv, u16 status) static void iwlagn_count_tx_err_status(struct iwl_priv *priv, u16 status)
{ {
status &= TX_STATUS_MSK; status &= TX_STATUS_MSK;
...@@ -889,7 +837,8 @@ void iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb) ...@@ -889,7 +837,8 @@ void iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb)
__skb_queue_head_init(&skbs); __skb_queue_head_init(&skbs);
/*we can free until ssn % q.n_bd not inclusive */ /*we can free until ssn % q.n_bd not inclusive */
iwl_trans_reclaim(trans(priv), txq_id, ssn, status, &skbs); iwl_trans_reclaim(trans(priv), sta_id, tid, txq_id,
ssn, status, &skbs);
freed = 0; freed = 0;
while (!skb_queue_empty(&skbs)) { while (!skb_queue_empty(&skbs)) {
skb = __skb_dequeue(&skbs); skb = __skb_dequeue(&skbs);
...@@ -939,9 +888,6 @@ void iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb) ...@@ -939,9 +888,6 @@ void iwlagn_rx_reply_tx(struct iwl_priv *priv, struct iwl_rx_mem_buffer *rxb)
} }
WARN_ON(!is_agg && freed != 1); WARN_ON(!is_agg && freed != 1);
iwl_free_tfds_in_queue(priv, sta_id, tid, freed);
iwlagn_txq_check_empty(priv, sta_id, tid, txq_id);
} }
iwl_check_abort_status(priv, tx_resp->frame_count, status); iwl_check_abort_status(priv, tx_resp->frame_count, status);
...@@ -1050,8 +996,8 @@ void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, ...@@ -1050,8 +996,8 @@ void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv,
/* Release all TFDs before the SSN, i.e. all TFDs in front of /* Release all TFDs before the SSN, i.e. all TFDs in front of
* block-ack window (we assume that they've been successfully * block-ack window (we assume that they've been successfully
* transmitted ... if not, it's too late anyway). */ * transmitted ... if not, it's too late anyway). */
iwl_trans_reclaim(trans(priv), scd_flow, ba_resp_scd_ssn, 0, iwl_trans_reclaim(trans(priv), sta_id, tid, scd_flow, ba_resp_scd_ssn,
&reclaimed_skbs); 0, &reclaimed_skbs);
freed = 0; freed = 0;
while (!skb_queue_empty(&reclaimed_skbs)) { while (!skb_queue_empty(&reclaimed_skbs)) {
...@@ -1082,8 +1028,5 @@ void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv, ...@@ -1082,8 +1028,5 @@ void iwlagn_rx_reply_compressed_ba(struct iwl_priv *priv,
ieee80211_tx_status_irqsafe(priv->hw, skb); ieee80211_tx_status_irqsafe(priv->hw, skb);
} }
iwl_free_tfds_in_queue(priv, sta_id, tid, freed);
iwlagn_txq_check_empty(priv, sta_id, tid, scd_flow);
spin_unlock_irqrestore(&priv->shrd->sta_lock, flags); spin_unlock_irqrestore(&priv->shrd->sta_lock, flags);
} }
...@@ -1858,11 +1858,30 @@ __le32 iwl_add_beacon_time(struct iwl_priv *priv, u32 base, ...@@ -1858,11 +1858,30 @@ __le32 iwl_add_beacon_time(struct iwl_priv *priv, u32 base,
return cpu_to_le32(res); return cpu_to_le32(res);
} }
void iwl_start_tx_ba_trans_ready(struct iwl_priv *priv, u8 ctx, void iwl_start_tx_ba_trans_ready(struct iwl_priv *priv,
enum iwl_rxon_context_id ctx,
u8 sta_id, u8 tid) u8 sta_id, u8 tid)
{ {
struct ieee80211_vif *vif = priv->contexts[ctx].vif; struct ieee80211_vif *vif = priv->contexts[ctx].vif;
u8 *addr = priv->stations[sta_id].sta.sta.addr; u8 *addr = priv->stations[sta_id].sta.sta.addr;
if (ctx == NUM_IWL_RXON_CTX)
ctx = priv->stations[sta_id].ctxid;
vif = priv->contexts[ctx].vif;
ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid); ieee80211_start_tx_ba_cb_irqsafe(vif, addr, tid);
} }
void iwl_stop_tx_ba_trans_ready(struct iwl_priv *priv,
enum iwl_rxon_context_id ctx,
u8 sta_id, u8 tid)
{
struct ieee80211_vif *vif;
u8 *addr = priv->stations[sta_id].sta.sta.addr;
if (ctx == NUM_IWL_RXON_CTX)
ctx = priv->stations[sta_id].ctxid;
vif = priv->contexts[ctx].vif;
ieee80211_stop_tx_ba_cb_irqsafe(vif, addr, tid);
}
...@@ -346,8 +346,12 @@ int iwl_probe(struct iwl_bus *bus, const struct iwl_trans_ops *trans_ops, ...@@ -346,8 +346,12 @@ int iwl_probe(struct iwl_bus *bus, const struct iwl_trans_ops *trans_ops,
struct iwl_cfg *cfg); struct iwl_cfg *cfg);
void __devexit iwl_remove(struct iwl_priv * priv); void __devexit iwl_remove(struct iwl_priv * priv);
void iwl_start_tx_ba_trans_ready(struct iwl_priv *priv, u8 ctx, void iwl_start_tx_ba_trans_ready(struct iwl_priv *priv,
enum iwl_rxon_context_id ctx,
u8 sta_id, u8 tid); u8 sta_id, u8 tid);
void iwl_stop_tx_ba_trans_ready(struct iwl_priv *priv,
enum iwl_rxon_context_id ctx,
u8 sta_id, u8 tid);
/***************************************************** /*****************************************************
* DRIVER STATUS FUNCTIONS * DRIVER STATUS FUNCTIONS
......
...@@ -202,8 +202,8 @@ void iwl_trans_pcie_txq_agg_setup(struct iwl_priv *priv, ...@@ -202,8 +202,8 @@ void iwl_trans_pcie_txq_agg_setup(struct iwl_priv *priv,
int sta_id, int tid, int frame_limit); int sta_id, int tid, int frame_limit);
void iwlagn_txq_free_tfd(struct iwl_trans *trans, struct iwl_tx_queue *txq, void iwlagn_txq_free_tfd(struct iwl_trans *trans, struct iwl_tx_queue *txq,
int index); int index);
void iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index,
struct sk_buff_head *skbs); struct sk_buff_head *skbs);
/***************************************************** /*****************************************************
* Error handling * Error handling
......
...@@ -1118,12 +1118,13 @@ int iwl_trans_pcie_send_cmd_pdu(struct iwl_trans *trans, u8 id, u32 flags, ...@@ -1118,12 +1118,13 @@ int iwl_trans_pcie_send_cmd_pdu(struct iwl_trans *trans, u8 id, u32 flags,
} }
/* Frees buffers until index _not_ inclusive */ /* Frees buffers until index _not_ inclusive */
void iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, int iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index,
struct sk_buff_head *skbs) struct sk_buff_head *skbs)
{ {
struct iwl_tx_queue *txq = &priv(trans)->txq[txq_id]; struct iwl_tx_queue *txq = &priv(trans)->txq[txq_id];
struct iwl_queue *q = &txq->q; struct iwl_queue *q = &txq->q;
int last_to_free; int last_to_free;
int freed = 0;
/*Since we free until index _not_ inclusive, the one before index is /*Since we free until index _not_ inclusive, the one before index is
* the last we will free. This one must be used */ * the last we will free. This one must be used */
...@@ -1135,14 +1136,14 @@ void iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, ...@@ -1135,14 +1136,14 @@ void iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index,
"last_to_free %d is out of range [0-%d] %d %d.\n", "last_to_free %d is out of range [0-%d] %d %d.\n",
__func__, txq_id, last_to_free, q->n_bd, __func__, txq_id, last_to_free, q->n_bd,
q->write_ptr, q->read_ptr); q->write_ptr, q->read_ptr);
return; return 0;
} }
IWL_DEBUG_TX_REPLY(trans, "reclaim: [%d, %d, %d]\n", txq_id, IWL_DEBUG_TX_REPLY(trans, "reclaim: [%d, %d, %d]\n", txq_id,
q->read_ptr, index); q->read_ptr, index);
if (WARN_ON(!skb_queue_empty(skbs))) if (WARN_ON(!skb_queue_empty(skbs)))
return; return 0;
for (; for (;
q->read_ptr != index; q->read_ptr != index;
...@@ -1158,5 +1159,7 @@ void iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index, ...@@ -1158,5 +1159,7 @@ void iwl_tx_queue_reclaim(struct iwl_trans *trans, int txq_id, int index,
iwlagn_txq_inval_byte_cnt_tbl(trans, txq); iwlagn_txq_inval_byte_cnt_tbl(trans, txq);
iwlagn_txq_free_tfd(trans, txq, txq->q.read_ptr); iwlagn_txq_free_tfd(trans, txq, txq->q.read_ptr);
freed++;
} }
return freed;
} }
...@@ -1265,19 +1265,75 @@ static int iwl_trans_pcie_request_irq(struct iwl_trans *trans) ...@@ -1265,19 +1265,75 @@ static int iwl_trans_pcie_request_irq(struct iwl_trans *trans)
return 0; return 0;
} }
static void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, static int iwlagn_txq_check_empty(struct iwl_trans *trans,
int ssn, u32 status, struct sk_buff_head *skbs) int sta_id, u8 tid, int txq_id)
{ {
struct iwl_priv *priv = priv(trans); struct iwl_queue *q = &priv(trans)->txq[txq_id].q;
struct iwl_tx_queue *txq = &priv->txq[txq_id]; struct iwl_tid_data *tid_data = &trans->shrd->tid_data[sta_id][tid];
lockdep_assert_held(&trans->shrd->sta_lock);
switch (trans->shrd->tid_data[sta_id][tid].agg.state) {
case IWL_EMPTYING_HW_QUEUE_DELBA:
/* We are reclaiming the last packet of the */
/* aggregated HW queue */
if ((txq_id == tid_data->agg.txq_id) &&
(q->read_ptr == q->write_ptr)) {
IWL_DEBUG_HT(trans,
"HW queue empty: continue DELBA flow\n");
iwl_trans_pcie_txq_agg_disable(priv(trans), txq_id);
tid_data->agg.state = IWL_AGG_OFF;
iwl_stop_tx_ba_trans_ready(priv(trans),
NUM_IWL_RXON_CTX,
sta_id, tid);
iwl_wake_queue(priv(trans), &priv(trans)->txq[txq_id]);
}
break;
case IWL_EMPTYING_HW_QUEUE_ADDBA:
/* We are reclaiming the last packet of the queue */
if (tid_data->tfds_in_queue == 0) {
IWL_DEBUG_HT(trans,
"HW queue empty: continue ADDBA flow\n");
tid_data->agg.state = IWL_AGG_ON;
iwl_start_tx_ba_trans_ready(priv(trans),
NUM_IWL_RXON_CTX,
sta_id, tid);
}
break;
}
return 0;
}
static void iwl_free_tfds_in_queue(struct iwl_trans *trans,
int sta_id, int tid, int freed)
{
lockdep_assert_held(&trans->shrd->sta_lock);
if (trans->shrd->tid_data[sta_id][tid].tfds_in_queue >= freed)
trans->shrd->tid_data[sta_id][tid].tfds_in_queue -= freed;
else {
IWL_DEBUG_TX(trans, "free more than tfds_in_queue (%u:%d)\n",
trans->shrd->tid_data[sta_id][tid].tfds_in_queue,
freed);
trans->shrd->tid_data[sta_id][tid].tfds_in_queue = 0;
}
}
static void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int sta_id, int tid,
int txq_id, int ssn, u32 status,
struct sk_buff_head *skbs)
{
struct iwl_tx_queue *txq = &priv(trans)->txq[txq_id];
/* n_bd is usually 256 => n_bd - 1 = 0xff */ /* n_bd is usually 256 => n_bd - 1 = 0xff */
int tfd_num = ssn & (txq->q.n_bd - 1); int tfd_num = ssn & (txq->q.n_bd - 1);
int freed = 0;
u8 agg_state; u8 agg_state;
bool cond; bool cond;
if (txq->sched_retry) { if (txq->sched_retry) {
agg_state = agg_state =
priv->shrd->tid_data[txq->sta_id][txq->tid].agg.state; trans->shrd->tid_data[txq->sta_id][txq->tid].agg.state;
cond = (agg_state != IWL_EMPTYING_HW_QUEUE_DELBA); cond = (agg_state != IWL_EMPTYING_HW_QUEUE_DELBA);
} else { } else {
cond = (status != TX_STATUS_FAIL_PASSIVE_NO_RX); cond = (status != TX_STATUS_FAIL_PASSIVE_NO_RX);
...@@ -1287,10 +1343,13 @@ static void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id, ...@@ -1287,10 +1343,13 @@ static void iwl_trans_pcie_reclaim(struct iwl_trans *trans, int txq_id,
IWL_DEBUG_TX_REPLY(trans, "Retry scheduler reclaim " IWL_DEBUG_TX_REPLY(trans, "Retry scheduler reclaim "
"scd_ssn=%d idx=%d txq=%d swq=%d\n", "scd_ssn=%d idx=%d txq=%d swq=%d\n",
ssn , tfd_num, txq_id, txq->swq_id); ssn , tfd_num, txq_id, txq->swq_id);
iwl_tx_queue_reclaim(trans, txq_id, tfd_num, skbs); freed = iwl_tx_queue_reclaim(trans, txq_id, tfd_num, skbs);
if (iwl_queue_space(&txq->q) > txq->q.low_mark && cond) if (iwl_queue_space(&txq->q) > txq->q.low_mark && cond)
iwl_wake_queue(priv, txq); iwl_wake_queue(priv(trans), txq);
} }
iwl_free_tfds_in_queue(trans, sta_id, tid, freed);
iwlagn_txq_check_empty(trans, sta_id, tid, txq_id);
} }
static void iwl_trans_pcie_free(struct iwl_trans *trans) static void iwl_trans_pcie_free(struct iwl_trans *trans)
......
...@@ -123,8 +123,9 @@ struct iwl_trans_ops { ...@@ -123,8 +123,9 @@ struct iwl_trans_ops {
const void *data); const void *data);
int (*tx)(struct iwl_trans *trans, struct sk_buff *skb, int (*tx)(struct iwl_trans *trans, struct sk_buff *skb,
struct iwl_device_cmd *dev_cmd, u8 ctx, u8 sta_id); struct iwl_device_cmd *dev_cmd, u8 ctx, u8 sta_id);
void (*reclaim)(struct iwl_trans *trans, int txq_id, int ssn, void (*reclaim)(struct iwl_trans *trans, int sta_id, int tid,
u32 status, struct sk_buff_head *skbs); int txq_id, int ssn, u32 status,
struct sk_buff_head *skbs);
int (*txq_agg_disable)(struct iwl_priv *priv, u16 txq_id); int (*txq_agg_disable)(struct iwl_priv *priv, u16 txq_id);
int (*tx_agg_alloc)(struct iwl_trans *trans, int (*tx_agg_alloc)(struct iwl_trans *trans,
...@@ -208,11 +209,11 @@ static inline int iwl_trans_tx(struct iwl_trans *trans, struct sk_buff *skb, ...@@ -208,11 +209,11 @@ static inline int iwl_trans_tx(struct iwl_trans *trans, struct sk_buff *skb,
return trans->ops->tx(trans, skb, dev_cmd, ctx, sta_id); return trans->ops->tx(trans, skb, dev_cmd, ctx, sta_id);
} }
static inline void iwl_trans_reclaim(struct iwl_trans *trans, int txq_id, static inline void iwl_trans_reclaim(struct iwl_trans *trans, int sta_id,
int ssn, u32 status, int tid, int txq_id, int ssn, u32 status,
struct sk_buff_head *skbs) struct sk_buff_head *skbs)
{ {
trans->ops->reclaim(trans, txq_id, ssn, status, skbs); trans->ops->reclaim(trans, sta_id, tid, txq_id, ssn, status, skbs);
} }
static inline int iwl_trans_txq_agg_disable(struct iwl_trans *trans, u16 txq_id) static inline int iwl_trans_txq_agg_disable(struct iwl_trans *trans, u16 txq_id)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment