@@ -831,6 +831,53 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
return ret;
}
+static bool _wcn36xx_dxe_tx_channel_is_empty(struct wcn36xx_dxe_ch *ch)
+{
+ int flags;
+ struct wcn36xx_dxe_ctl *ctl_bd_start, *ctl_skb_start;
+ struct wcn36xx_dxe_ctl *ctl_bd, *ctl_skb;
+ bool ret = true;
+
+ spin_lock_irqsave(&ch->lock, flags);
+
+ /* Loop through ring buffer looking for nonempty entries. */
+ ctl_bd_start = ch->head_blk_ctl;
+ ctl_bd = ctl_bd_start;
+ ctl_skb_start = ctl_bd_start->next;
+ ctl_skb = ctl_skb_start;
+ do {
+ if (ctl_skb->skb) {
+ ret = false;
+ goto unlock;
+ }
+ ctl_bd = ctl_skb->next;
+ ctl_skb = ctl_bd->next;
+ } while (ctl_skb != ctl_skb_start);
+
+unlock:
+ spin_unlock_irqrestore(&ch->lock, flags);
+ return ret;
+}
+
+int wcn36xx_dxe_tx_flush(struct wcn36xx *wcn)
+{
+ int i = 0;
+
+ /* Called with mac80211 queues stopped. Wait for empty HW queues. */
+ do {
+ if (_wcn36xx_dxe_tx_channel_is_empty(&wcn->dxe_tx_l_ch) &&
+ _wcn36xx_dxe_tx_channel_is_empty(&wcn->dxe_tx_h_ch)) {
+ return 0;
+ }
+ /* This ieee80211_ops callback is specifically allowed to
+ * sleep.
+ */
+ usleep_range(1000, 1100);
+ } while (++i < 100);
+
+ return -EBUSY;
+}
+
int wcn36xx_dxe_init(struct wcn36xx *wcn)
{
int reg_data = 0, ret;
@@ -466,5 +466,6 @@ int wcn36xx_dxe_tx_frame(struct wcn36xx *wcn,
struct wcn36xx_tx_bd *bd,
struct sk_buff *skb,
bool is_low);
+int wcn36xx_dxe_tx_flush(struct wcn36xx *wcn);
void wcn36xx_dxe_tx_ack_ind(struct wcn36xx *wcn, u32 status);
#endif /* _DXE_H_ */
@@ -1275,6 +1275,16 @@ static void wcn36xx_ipv6_addr_change(struct ieee80211_hw *hw,
}
#endif
+static void wcn36xx_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ u32 queues, bool drop)
+{
+ struct wcn36xx *wcn = hw->priv;
+
+ if (wcn36xx_dxe_tx_flush(wcn)) {
+ wcn36xx_err("Failed to flush hardware tx queues\n");
+ }
+}
+
static const struct ieee80211_ops wcn36xx_ops = {
.start = wcn36xx_start,
.stop = wcn36xx_stop,
@@ -1302,6 +1312,7 @@ static const struct ieee80211_ops wcn36xx_ops = {
#if IS_ENABLED(CONFIG_IPV6)
.ipv6_addr_change = wcn36xx_ipv6_addr_change,
#endif
+ .flush = wcn36xx_flush,
CFG80211_TESTMODE_CMD(wcn36xx_tm_cmd)
};
Without ieee80211_ops->flush implemented to empty HW queues, mac80211 will do a 100ms dead wait after stopping SW queues, before leaving the operating channel to resume a software connected scan[1]. (see ieee80211_scan_state_resume) This wait is correctly included in the calculation for whether or not we've exceeded max off-channel time, as it occurs after sending the null frame with PS bit set. Thus, with 125 ms max off-channel time we only have 25 ms of scan time, which technically isn't even enough to scan one channel (although mac80211 always scans at least one channel per off- channel window). Moreover, for passive probes we end up spending at least 100 ms + 111 ms (IEEE80211_PASSIVE_CHANNEL_TIME) "off-channel"[2], which exceeds the listen interval of 200 ms that we provide in our association request frame. That's technically out-of-spec. [1]: Until recently, wcn36xx performed software (rather than FW-offloaded) scanning when 5GHz channels are requested. This apparent limitation is now resolved -- see commit 1395f8a6a4d5 ("wcn36xx: Enable hardware scan offload for 5Ghz band"). [2]: in quotes because about 100 ms of it is still on-channel but with PS set Signed-off-by: Benjamin Li <benl@squareup.com> --- drivers/net/wireless/ath/wcn36xx/dxe.c | 47 +++++++++++++++++++++++++ drivers/net/wireless/ath/wcn36xx/dxe.h | 1 + drivers/net/wireless/ath/wcn36xx/main.c | 11 ++++++ 3 files changed, 59 insertions(+)