2011-01-31 11:16:37

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 00/10] wl12xx: AP-mode per link PSM

This patchset introduces per-link PSM for AP-mode. The PS status of each
link is updated from FW.
The wl12xx concept of PSM is different from the mac80211 one - some data
must remain in FW for a link in PSM since the TIM is updated automatically
by FW. An exported function to manually toggle PSM in mac80211 is added
to bridge this gap.

We set up a skb-queue per link which allows for a cleaner link
disconnection and prepares the code for regulating each link separately.

v1->2: Changed mac80211 PS state change exported functions

A few other AP-mode related small bug fixes and enchancements are included.
Arik Nemtsov (10):
wl12xx: fix potential race condition with TX queue watermark
wl12xx: AP-mode - fix race condition on sta connection
wl12xx: AP-mode - TX queue per link in AC
mac80211: do not calc frame duration when using HW rate-control
wl12xx: report invalid TX rate when returning non-TX-ed skbs
mac80211: add HW flag for disabling auto link-PS in AP mode
wl12xx: AP-mode - support HW based link PS monitoring
wl12xx: AP mode - fix bug in cleanup of wl1271_op_sta_add()
wl12xx: AP-mode - count free FW TX blocks per link
wl12xx: AP-mode - management of links in PS-mode

drivers/net/wireless/wl12xx/acx.c | 25 ++++
drivers/net/wireless/wl12xx/acx.h | 9 ++
drivers/net/wireless/wl12xx/main.c | 125 +++++++++++++++---
drivers/net/wireless/wl12xx/ps.c | 78 +++++++++++
drivers/net/wireless/wl12xx/ps.h | 2 +
drivers/net/wireless/wl12xx/tx.c | 235 +++++++++++++++++++++++++++++-----
drivers/net/wireless/wl12xx/tx.h | 3 +
drivers/net/wireless/wl12xx/wl12xx.h | 37 ++++++
include/net/mac80211.h | 54 ++++++++-
net/mac80211/rx.c | 27 ++++-
net/mac80211/sta_info.c | 3 +-
net/mac80211/status.c | 4 +
net/mac80211/tx.c | 3 +-
13 files changed, 543 insertions(+), 62 deletions(-)



2011-01-31 11:16:44

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 03/10] wl12xx: AP-mode - TX queue per link in AC

When operating in AP-mode we require a per link tx-queue.
This allows us to implement HW assisted PS mode for links,
as well as regulate per-link FW TX blocks consumption.
Split each link into ACs to support future QoS for AP-mode.

AC queues are emptied in priority and per-link queues are
scheduled in a simple round-robin fashion.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 17 ++++-
drivers/net/wireless/wl12xx/tx.c | 130 +++++++++++++++++++++++++++++++---
drivers/net/wireless/wl12xx/tx.h | 3 +
drivers/net/wireless/wl12xx/wl12xx.h | 14 ++++
4 files changed, 151 insertions(+), 13 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index cf11bda..2c6bde9 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -966,6 +966,7 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
struct ieee80211_sta *sta = txinfo->control.sta;
unsigned long flags;
int q;
+ u8 hlid = 0;

/*
* peek into the rates configured in the STA entry.
@@ -1010,7 +1011,13 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)

/* queue the packet */
q = wl1271_tx_get_queue(skb_get_queue_mapping(skb));
- skb_queue_tail(&wl->tx_queue[q], skb);
+ if (wl->bss_type == BSS_TYPE_AP_BSS) {
+ hlid = wl1271_tx_get_hlid(skb);
+ wl1271_debug(DEBUG_TX, "queue skb hlid %d q %d", hlid, q);
+ skb_queue_tail(&wl->links[hlid].tx_queue[q], skb);
+ } else {
+ skb_queue_tail(&wl->tx_queue[q], skb);
+ }

/*
* The chip specific setup must run before the first TX packet -
@@ -2640,6 +2647,7 @@ static void wl1271_free_hlid(struct wl1271 *wl, u8 hlid)
int id = hlid - WL1271_AP_STA_HLID_START;

__clear_bit(id, wl->ap_hlid_map);
+ wl1271_tx_reset_link_queues(wl, hlid);
}

static int wl1271_op_sta_add(struct ieee80211_hw *hw,
@@ -3265,7 +3273,7 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
struct ieee80211_hw *hw;
struct platform_device *plat_dev = NULL;
struct wl1271 *wl;
- int i, ret;
+ int i, j, ret;
unsigned int order;

hw = ieee80211_alloc_hw(sizeof(*wl), &wl1271_ops);
@@ -3293,6 +3301,10 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
for (i = 0; i < NUM_TX_QUEUES; i++)
skb_queue_head_init(&wl->tx_queue[i]);

+ for (i = 0; i < NUM_TX_QUEUES; i++)
+ for (j = 0; j < AP_MAX_LINKS; j++)
+ skb_queue_head_init(&wl->links[j].tx_queue[i]);
+
INIT_DELAYED_WORK(&wl->elp_work, wl1271_elp_work);
INIT_DELAYED_WORK(&wl->pspoll_work, wl1271_pspoll_work);
INIT_WORK(&wl->irq_work, wl1271_irq_work);
@@ -3319,6 +3331,7 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
wl->bss_type = MAX_BSS_TYPE;
wl->set_bss_type = MAX_BSS_TYPE;
wl->fw_bss_type = MAX_BSS_TYPE;
+ wl->last_tx_hlid = 0;

memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map));
for (i = 0; i < ACX_TX_DESCRIPTORS; i++)
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index 820f455..c658821 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -86,6 +86,27 @@ static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl,
wl1271_acx_set_inconnection_sta(wl, hdr->addr1);
}

+u8 wl1271_tx_get_hlid(struct sk_buff *skb)
+{
+ struct ieee80211_tx_info *control = IEEE80211_SKB_CB(skb);
+
+ if (control->control.sta) {
+ struct wl1271_station *wl_sta;
+
+ wl_sta = (struct wl1271_station *)
+ control->control.sta->drv_priv;
+ return wl_sta->hlid;
+ } else {
+ struct ieee80211_hdr *hdr;
+
+ hdr = (struct ieee80211_hdr *)skb->data;
+ if (ieee80211_is_mgmt(hdr->frame_control))
+ return WL1271_AP_GLOBAL_HLID;
+ else
+ return WL1271_AP_BROADCAST_HLID;
+ }
+}
+
static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
u32 buf_offset)
{
@@ -298,7 +319,7 @@ u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set)
return enabled_rates;
}

-static void handle_tx_low_watermark(struct wl1271 *wl)
+void wl1271_handle_tx_low_watermark(struct wl1271 *wl)
{
unsigned long flags;

@@ -312,7 +333,7 @@ static void handle_tx_low_watermark(struct wl1271 *wl)
}
}

-static struct sk_buff *wl1271_skb_dequeue(struct wl1271 *wl)
+static struct sk_buff *wl1271_sta_skb_dequeue(struct wl1271 *wl)
{
struct sk_buff *skb = NULL;
unsigned long flags;
@@ -338,12 +359,69 @@ out:
return skb;
}

+static struct sk_buff *wl1271_ap_skb_dequeue(struct wl1271 *wl)
+{
+ struct sk_buff *skb = NULL;
+ unsigned long flags;
+ int i, h, start_hlid;
+
+ /* start from the link after the last one */
+ start_hlid = (wl->last_tx_hlid + 1) % AP_MAX_LINKS;
+
+ /* dequeue according to AC, round robin on each link */
+ for (i = 0; i < AP_MAX_LINKS; i++) {
+ h = (start_hlid + i) % AP_MAX_LINKS;
+
+ skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_VO]);
+ if (skb)
+ goto out;
+ skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_VI]);
+ if (skb)
+ goto out;
+ skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_BE]);
+ if (skb)
+ goto out;
+ skb = skb_dequeue(&wl->links[h].tx_queue[CONF_TX_AC_BK]);
+ if (skb)
+ goto out;
+ }
+
+out:
+ if (skb) {
+ wl->last_tx_hlid = h;
+ spin_lock_irqsave(&wl->wl_lock, flags);
+ wl->tx_queue_count--;
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
+ } else {
+ wl->last_tx_hlid = 0;
+ }
+
+ return skb;
+}
+
+static struct sk_buff *wl1271_skb_dequeue(struct wl1271 *wl)
+{
+ if (wl->bss_type == BSS_TYPE_AP_BSS)
+ return wl1271_ap_skb_dequeue(wl);
+
+ return wl1271_sta_skb_dequeue(wl);
+}
+
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));

- skb_queue_head(&wl->tx_queue[q], skb);
+ 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);
+
+ /* make sure we dequeue the same packet next time */
+ wl->last_tx_hlid = (hlid + AP_MAX_LINKS - 1) % AP_MAX_LINKS;
+ } else {
+ skb_queue_head(&wl->tx_queue[q], skb);
+ }
+
spin_lock_irqsave(&wl->wl_lock, flags);
wl->tx_queue_count++;
spin_unlock_irqrestore(&wl->wl_lock, flags);
@@ -428,7 +506,7 @@ out_ack:
if (sent_packets) {
/* interrupt the firmware with the new packets */
wl1271_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count);
- handle_tx_low_watermark(wl);
+ wl1271_handle_tx_low_watermark(wl);
}

out:
@@ -545,6 +623,27 @@ void wl1271_tx_complete(struct wl1271 *wl)
}
}

+void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid)
+{
+ struct sk_buff *skb;
+ int i, total = 0;
+ unsigned long flags;
+
+ for (i = 0; i < NUM_TX_QUEUES; i++) {
+ while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) {
+ wl1271_debug(DEBUG_TX, "link freeing skb 0x%p", skb);
+ ieee80211_tx_status(wl->hw, skb);
+ total++;
+ }
+ }
+
+ spin_lock_irqsave(&wl->wl_lock, flags);
+ wl->tx_queue_count -= total;
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
+
+ wl1271_handle_tx_low_watermark(wl);
+}
+
/* caller must hold wl->mutex */
void wl1271_tx_reset(struct wl1271 *wl)
{
@@ -552,19 +651,28 @@ void wl1271_tx_reset(struct wl1271 *wl)
struct sk_buff *skb;

/* TX failure */
- for (i = 0; i < NUM_TX_QUEUES; i++) {
- while ((skb = skb_dequeue(&wl->tx_queue[i]))) {
- wl1271_debug(DEBUG_TX, "freeing skb 0x%p", skb);
- ieee80211_tx_status(wl->hw, skb);
+ if (wl->bss_type == BSS_TYPE_AP_BSS) {
+ for (i = 0; i < AP_MAX_LINKS; i++)
+ wl1271_tx_reset_link_queues(wl, i);
+
+ wl->last_tx_hlid = 0;
+ } else {
+ for (i = 0; i < NUM_TX_QUEUES; i++) {
+ while ((skb = skb_dequeue(&wl->tx_queue[i]))) {
+ wl1271_debug(DEBUG_TX, "freeing skb 0x%p",
+ skb);
+ ieee80211_tx_status(wl->hw, skb);
+ }
}
}
+
wl->tx_queue_count = 0;

/*
* Make sure the driver is at a consistent state, in case this
* function is called from a context other than interface removal.
*/
- handle_tx_low_watermark(wl);
+ wl1271_handle_tx_low_watermark(wl);

for (i = 0; i < ACX_TX_DESCRIPTORS; i++)
if (wl->tx_frames[i] != NULL) {
@@ -585,8 +693,8 @@ void wl1271_tx_flush(struct wl1271 *wl)

while (!time_after(jiffies, timeout)) {
mutex_lock(&wl->mutex);
- wl1271_debug(DEBUG_TX, "flushing tx buffer: %d",
- wl->tx_frames_cnt);
+ wl1271_debug(DEBUG_TX, "flushing tx buffer: %d %d",
+ wl->tx_frames_cnt, wl->tx_queue_count);
if ((wl->tx_frames_cnt == 0) && (wl->tx_queue_count == 0)) {
mutex_unlock(&wl->mutex);
return;
diff --git a/drivers/net/wireless/wl12xx/tx.h b/drivers/net/wireless/wl12xx/tx.h
index 05722a5..76df5d5 100644
--- a/drivers/net/wireless/wl12xx/tx.h
+++ b/drivers/net/wireless/wl12xx/tx.h
@@ -152,5 +152,8 @@ void wl1271_tx_flush(struct wl1271 *wl);
u8 wl1271_rate_to_idx(int rate, enum ieee80211_band band);
u32 wl1271_tx_enabled_rates_get(struct wl1271 *wl, u32 rate_set);
u32 wl1271_tx_min_rate_get(struct wl1271 *wl);
+u8 wl1271_tx_get_hlid(struct sk_buff *skb);
+void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid);
+void wl1271_handle_tx_low_watermark(struct wl1271 *wl);

#endif
diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h
index d1de13f..30450e4 100644
--- a/drivers/net/wireless/wl12xx/wl12xx.h
+++ b/drivers/net/wireless/wl12xx/wl12xx.h
@@ -278,6 +278,11 @@ struct wl1271_ap_key {
u16 tx_seq_16;
};

+struct wl1271_link {
+ /* AP-mode - TX queue per AC in link */
+ struct sk_buff_head tx_queue[NUM_TX_QUEUES];
+};
+
struct wl1271 {
struct platform_device *plat_dev;
struct ieee80211_hw *hw;
@@ -474,6 +479,15 @@ struct wl1271 {
/* RX BA constraint value */
bool ba_support;
u8 ba_rx_bitmap;
+
+ /*
+ * AP-mode - links indexed by HLID. The global and broadcast links
+ * are always active.
+ */
+ struct wl1271_link links[AP_MAX_LINKS];
+
+ /* the hlid of the link where the last transmitted skb came from */
+ int last_tx_hlid;
};

struct wl1271_station {
--
1.7.1


2011-01-31 11:16:57

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 07/10] wl12xx: AP-mode - support HW based link PS monitoring

When operating in AP mode the wl1271 hardware filters out null-data
packets as well as management packets. This makes it impossible for
mac80211 to monitor the PS mode by using the PM bit of incoming frames.

Disable mac80211 automatic link PS-mode handling by supporting
IEEE80211_HW_AP_LINK_PS in HW flags.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 2c6bde9..8d55c89 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -3221,7 +3221,8 @@ int wl1271_init_ieee80211(struct wl1271 *wl)
IEEE80211_HW_SUPPORTS_UAPSD |
IEEE80211_HW_HAS_RATE_CONTROL |
IEEE80211_HW_CONNECTION_MONITOR |
- IEEE80211_HW_SUPPORTS_CQM_RSSI;
+ IEEE80211_HW_SUPPORTS_CQM_RSSI |
+ IEEE80211_HW_AP_LINK_PS;

wl->hw->wiphy->cipher_suites = cipher_suites;
wl->hw->wiphy->n_cipher_suites = ARRAY_SIZE(cipher_suites);
--
1.7.1


2011-01-31 11:17:00

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 08/10] wl12xx: AP mode - fix bug in cleanup of wl1271_op_sta_add()

Remove an active hlid when chip wakeup fails. In addition rename the
involved functions.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 17 ++++++++++++-----
1 files changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 8d55c89..46eb499 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -2621,7 +2621,7 @@ static int wl1271_op_get_survey(struct ieee80211_hw *hw, int idx,
return 0;
}

-static int wl1271_allocate_hlid(struct wl1271 *wl,
+static int wl1271_allocate_sta(struct wl1271 *wl,
struct ieee80211_sta *sta,
u8 *hlid)
{
@@ -2642,10 +2642,13 @@ static int wl1271_allocate_hlid(struct wl1271 *wl,
return 0;
}

-static void wl1271_free_hlid(struct wl1271 *wl, u8 hlid)
+static void wl1271_free_sta(struct wl1271 *wl, u8 hlid)
{
int id = hlid - WL1271_AP_STA_HLID_START;

+ if (WARN_ON(!test_bit(id, wl->ap_hlid_map)))
+ return;
+
__clear_bit(id, wl->ap_hlid_map);
wl1271_tx_reset_link_queues(wl, hlid);
}
@@ -2668,13 +2671,13 @@ static int wl1271_op_sta_add(struct ieee80211_hw *hw,

wl1271_debug(DEBUG_MAC80211, "mac80211 add sta %d", (int)sta->aid);

- ret = wl1271_allocate_hlid(wl, sta, &hlid);
+ ret = wl1271_allocate_sta(wl, sta, &hlid);
if (ret < 0)
goto out;

ret = wl1271_ps_elp_wakeup(wl, false);
if (ret < 0)
- goto out;
+ goto out_free_sta;

ret = wl1271_cmd_add_sta(wl, sta, hlid);
if (ret < 0)
@@ -2683,6 +2686,10 @@ static int wl1271_op_sta_add(struct ieee80211_hw *hw,
out_sleep:
wl1271_ps_elp_sleep(wl);

+out_free_sta:
+ if (ret < 0)
+ wl1271_free_sta(wl, hlid);
+
out:
mutex_unlock(&wl->mutex);
return ret;
@@ -2719,7 +2726,7 @@ static int wl1271_op_sta_remove(struct ieee80211_hw *hw,
if (ret < 0)
goto out_sleep;

- wl1271_free_hlid(wl, wl_sta->hlid);
+ wl1271_free_sta(wl, wl_sta->hlid);

out_sleep:
wl1271_ps_elp_sleep(wl);
--
1.7.1


2011-01-31 11:17:05

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 10/10] wl12xx: AP-mode - management of links in PS-mode

Update the PS mode of each link according to a bitmap polled from
fw_status. Manually notify mac80211 about PS mode changes in connected
stations.

mac80211 will only be notified about PS start when the station is in PS
and there is a small number of TX blocks from this link ready in HW.
This is required for waking up the remote station since the TIM is
updated entirely by FW.

When a station enters mac80211-PS-mode, we drop all the skbs in the
low-level TX queues belonging to this sta with STAT_TX_FILTERED
to keep our queues clean.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 73 ++++++++++++++++++++++++++-----
drivers/net/wireless/wl12xx/ps.c | 80 ++++++++++++++++++++++++++++++++++
drivers/net/wireless/wl12xx/ps.h | 2 +
drivers/net/wireless/wl12xx/tx.c | 24 ++++++++++-
drivers/net/wireless/wl12xx/wl12xx.h | 19 ++++++++
5 files changed, 186 insertions(+), 12 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index f68f1fe..e058853 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -522,6 +522,57 @@ static int wl1271_plt_init(struct wl1271 *wl)
return ret;
}

+static void wl1271_irq_ps_regulate_link(struct wl1271 *wl, u8 hlid, u8 tx_blks)
+{
+ bool fw_ps;
+
+ /* only regulate station links */
+ if (hlid < WL1271_AP_STA_HLID_START)
+ return;
+
+ fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map);
+
+ /*
+ * Wake up from high level PS if the STA is asleep with too little
+ * blocks in FW or if the STA is awake.
+ */
+ if (!fw_ps || tx_blks < WL1271_PS_STA_MAX_BLOCKS)
+ wl1271_ps_link_end(wl, hlid);
+
+ /* Start high-level PS if the STA is asleep with enough blocks in FW */
+ else if (fw_ps && tx_blks >= WL1271_PS_STA_MAX_BLOCKS)
+ wl1271_ps_link_start(wl, hlid, true);
+}
+
+static void wl1271_irq_update_links_status(struct wl1271 *wl,
+ struct wl1271_fw_status *status)
+{
+ u32 cur_fw_ps_map;
+ u8 hlid;
+
+ cur_fw_ps_map = le32_to_cpu(status->link_ps_bitmap);
+ if (wl->ap_fw_ps_map != cur_fw_ps_map) {
+ wl1271_debug(DEBUG_PSM,
+ "link ps prev 0x%x cur 0x%x changed 0x%x",
+ wl->ap_fw_ps_map, cur_fw_ps_map,
+ wl->ap_fw_ps_map ^ cur_fw_ps_map);
+
+ wl->ap_fw_ps_map = cur_fw_ps_map;
+ }
+
+ for (hlid = WL1271_AP_STA_HLID_START; hlid < AP_MAX_LINKS; hlid++) {
+ u8 cnt = status->tx_lnk_free_blks[hlid] -
+ wl->links[hlid].prev_freed_blks;
+
+ wl->links[hlid].prev_freed_blks =
+ status->tx_lnk_free_blks[hlid];
+ wl->links[hlid].allocated_blks -= cnt;
+
+ wl1271_irq_ps_regulate_link(wl, hlid,
+ wl->links[hlid].allocated_blks);
+ }
+}
+
static void wl1271_fw_status(struct wl1271 *wl,
struct wl1271_fw_status *status)
{
@@ -553,16 +604,9 @@ static void wl1271_fw_status(struct wl1271 *wl,
if (total)
clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags);

- if (wl->bss_type == BSS_TYPE_AP_BSS) {
- for (i = 0; i < AP_MAX_LINKS; i++) {
- u8 cnt = status->tx_lnk_free_blks[i] -
- wl->links[i].prev_freed_blks;
-
- wl->links[i].prev_freed_blks =
- status->tx_lnk_free_blks[i];
- wl->links[i].allocated_blks -= cnt;
- }
- }
+ /* for AP update num of allocated TX blocks per link and ps status */
+ if (wl->bss_type == BSS_TYPE_AP_BSS)
+ wl1271_irq_update_links_status(wl, status);

/* update the host-chipset time offset */
getnstimeofday(&ts);
@@ -1250,6 +1294,8 @@ static void __wl1271_op_remove_interface(struct wl1271 *wl)
wl->filters = 0;
wl1271_free_ap_keys(wl);
memset(wl->ap_hlid_map, 0, sizeof(wl->ap_hlid_map));
+ wl->ap_fw_ps_map = 0;
+ wl->ap_ps_map = 0;

for (i = 0; i < NUM_TX_QUEUES; i++)
wl->tx_blocks_freed[i] = 0;
@@ -2646,10 +2692,10 @@ static int wl1271_allocate_sta(struct wl1271 *wl,
}

wl_sta = (struct wl1271_station *)sta->drv_priv;
-
__set_bit(id, wl->ap_hlid_map);
wl_sta->hlid = WL1271_AP_STA_HLID_START + id;
*hlid = wl_sta->hlid;
+ memcpy(wl->links[wl_sta->hlid].addr, sta->addr, ETH_ALEN);
return 0;
}

@@ -2661,7 +2707,10 @@ static void wl1271_free_sta(struct wl1271 *wl, u8 hlid)
return;

__clear_bit(id, wl->ap_hlid_map);
+ memset(wl->links[hlid].addr, 0, ETH_ALEN);
wl1271_tx_reset_link_queues(wl, hlid);
+ __clear_bit(hlid, &wl->ap_ps_map);
+ __clear_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map);
}

static int wl1271_op_sta_add(struct ieee80211_hw *hw,
@@ -3351,6 +3400,8 @@ struct ieee80211_hw *wl1271_alloc_hw(void)
wl->set_bss_type = MAX_BSS_TYPE;
wl->fw_bss_type = MAX_BSS_TYPE;
wl->last_tx_hlid = 0;
+ wl->ap_ps_map = 0;
+ wl->ap_fw_ps_map = 0;

memset(wl->tx_frames_map, 0, sizeof(wl->tx_frames_map));
for (i = 0; i < ACX_TX_DESCRIPTORS; i++)
diff --git a/drivers/net/wireless/wl12xx/ps.c b/drivers/net/wireless/wl12xx/ps.c
index 92397ad..ef39c59 100644
--- a/drivers/net/wireless/wl12xx/ps.c
+++ b/drivers/net/wireless/wl12xx/ps.c
@@ -24,6 +24,7 @@
#include "reg.h"
#include "ps.h"
#include "io.h"
+#include "tx.h"

#define WL1271_WAKEUP_TIMEOUT 500

@@ -174,3 +175,82 @@ int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode,

return ret;
}
+
+static void wl1271_ps_filter_frames(struct wl1271 *wl, u8 hlid)
+{
+ int i, filtered = 0;
+ struct sk_buff *skb;
+ struct ieee80211_tx_info *info;
+ unsigned long flags;
+
+ /* filter all frames currently the low level queus for this hlid */
+ for (i = 0; i < NUM_TX_QUEUES; i++) {
+ while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) {
+ info = IEEE80211_SKB_CB(skb);
+ info->flags |= IEEE80211_TX_STAT_TX_FILTERED;
+ info->status.rates[0].idx = -1;
+ ieee80211_tx_status(wl->hw, skb);
+ filtered++;
+ }
+ }
+
+ spin_lock_irqsave(&wl->wl_lock, flags);
+ wl->tx_queue_count -= filtered;
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
+
+ wl1271_handle_tx_low_watermark(wl);
+}
+
+void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues)
+{
+ struct ieee80211_sta *sta;
+
+ if (test_bit(hlid, &wl->ap_ps_map))
+ return;
+
+ wl1271_debug(DEBUG_PSM, "start mac80211 PSM on hlid %d blks %d "
+ "clean_queues %d", hlid, wl->links[hlid].allocated_blks,
+ clean_queues);
+
+ rcu_read_lock();
+ sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr);
+ if (!sta) {
+ wl1271_error("could not find sta %pM for starting ps",
+ wl->links[hlid].addr);
+ rcu_read_unlock();
+ return;
+ }
+
+ ieee80211_sta_ps_transition_ni(sta, true);
+ rcu_read_unlock();
+
+ /* do we want to filter all frames from this link's queues? */
+ if (clean_queues)
+ wl1271_ps_filter_frames(wl, hlid);
+
+ __set_bit(hlid, &wl->ap_ps_map);
+}
+
+void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid)
+{
+ struct ieee80211_sta *sta;
+
+ if (!test_bit(hlid, &wl->ap_ps_map))
+ return;
+
+ wl1271_debug(DEBUG_PSM, "end mac80211 PSM on hlid %d", hlid);
+
+ __clear_bit(hlid, &wl->ap_ps_map);
+
+ rcu_read_lock();
+ sta = ieee80211_find_sta(wl->vif, wl->links[hlid].addr);
+ if (!sta) {
+ wl1271_error("could not find sta %pM for ending ps",
+ wl->links[hlid].addr);
+ goto end;
+ }
+
+ ieee80211_sta_ps_transition_ni(sta, false);
+end:
+ rcu_read_unlock();
+}
diff --git a/drivers/net/wireless/wl12xx/ps.h b/drivers/net/wireless/wl12xx/ps.h
index 8415060..fc1f4c1 100644
--- a/drivers/net/wireless/wl12xx/ps.h
+++ b/drivers/net/wireless/wl12xx/ps.h
@@ -32,5 +32,7 @@ int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode,
void wl1271_ps_elp_sleep(struct wl1271 *wl);
int wl1271_ps_elp_wakeup(struct wl1271 *wl, bool chip_awake);
void wl1271_elp_work(struct work_struct *work);
+void wl1271_ps_link_start(struct wl1271 *wl, u8 hlid, bool clean_queues);
+void wl1271_ps_link_end(struct wl1271 *wl, u8 hlid);

#endif /* __WL1271_PS_H__ */
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index a91f179..1d69cc0 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -86,6 +86,26 @@ static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl,
wl1271_acx_set_inconnection_sta(wl, hdr->addr1);
}

+static void wl1271_tx_regulate_link(struct wl1271 *wl, u8 hlid)
+{
+ bool fw_ps;
+ u8 tx_blks;
+
+ /* only regulate station links */
+ if (hlid < WL1271_AP_STA_HLID_START)
+ return;
+
+ fw_ps = test_bit(hlid, (unsigned long *)&wl->ap_fw_ps_map);
+ tx_blks = wl->links[hlid].allocated_blks;
+
+ /*
+ * if in FW PS and there is enough data in FW we can put the link
+ * into high-level PS and clean out its TX queues.
+ */
+ if (fw_ps && tx_blks >= WL1271_PS_STA_MAX_BLOCKS)
+ wl1271_ps_link_start(wl, hlid, true);
+}
+
u8 wl1271_tx_get_hlid(struct sk_buff *skb)
{
struct ieee80211_tx_info *control = IEEE80211_SKB_CB(skb);
@@ -277,8 +297,10 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,
if (ret < 0)
return ret;

- if (wl->bss_type == BSS_TYPE_AP_BSS)
+ if (wl->bss_type == BSS_TYPE_AP_BSS) {
wl1271_tx_ap_update_inconnection_sta(wl, skb);
+ wl1271_tx_regulate_link(wl, hlid);
+ }

wl1271_tx_fill_hdr(wl, skb, extra, info, hlid);

diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h
index d169655..3496e64 100644
--- a/drivers/net/wireless/wl12xx/wl12xx.h
+++ b/drivers/net/wireless/wl12xx/wl12xx.h
@@ -153,6 +153,17 @@ extern u32 wl12xx_debug_level;
#define WL1271_AP_BROADCAST_HLID 1
#define WL1271_AP_STA_HLID_START 2

+/*
+ * When in AP-mode, we allow (at least) this number of mem-blocks
+ * to be transmitted to FW for a STA in PS-mode. Only when packets are
+ * present in the FW buffers it will wake the sleeping STA. We want to put
+ * enough packets for the driver to transmit all of its buffered data before
+ * the STA goes to sleep again. But we don't want to take too much mem-blocks
+ * as it might hurt the throughput of active STAs.
+ * The number of blocks (18) is enough for 2 large packets.
+ */
+#define WL1271_PS_STA_MAX_BLOCKS (2 * 9)
+
#define WL1271_AP_BSS_INDEX 0
#define WL1271_AP_DEF_INACTIV_SEC 300
#define WL1271_AP_DEF_BEACON_EXP 20
@@ -285,6 +296,8 @@ struct wl1271_link {
/* accounting for allocated / available TX blocks in FW */
u8 allocated_blks;
u8 prev_freed_blks;
+
+ u8 addr[ETH_ALEN];
};

struct wl1271 {
@@ -492,6 +505,12 @@ struct wl1271 {

/* the hlid of the link where the last transmitted skb came from */
int last_tx_hlid;
+
+ /* AP-mode - a bitmap of links currently in PS mode according to FW */
+ u32 ap_fw_ps_map;
+
+ /* AP-mode - a bitmap of links currently in PS mode in mac80211 */
+ unsigned long ap_ps_map;
};

struct wl1271_station {
--
1.7.1


2011-01-31 20:16:18

by Arik Nemtsov

[permalink] [raw]
Subject: Re: [PATCH v2 06/10] mac80211: add HW flag for disabling auto link-PS in AP mode

On Mon, Jan 31, 2011 at 22:06, Luciano Coelho <[email protected]> wrote:
> On Mon, 2011-01-31 at 13:11 +0100, Johannes Berg wrote:
>> On Mon, 2011-01-31 at 13:49 +0200, Arik Nemtsov wrote:
>> > On Mon, Jan 31, 2011 at 13:24, Johannes Berg
>> <[email protected]> wrote:
>> > > It'd be nicer if you could separate out mac80211 patches, but then
>> Luca
>> > > needs to hold off merging until John picked up the mac80211 patch.
>> >
>> > Yea that would complicate matters a bit. Does the ordering of the
>> > patches bother you?
>>
>> It just makes it more complicated for me to pick out the right thing
>> --
>> I almost missed that there was a mac80211 patch in this huge series.
>
> Yes, I totally agree with Johannes. ?I prefer that
> mac80211/cfg80211/whatever_other_stuff be sent as separate series, so
> that there's not confusions about who applies what. ?If the patches are
> in the same series, I always need to ask John to either take them all or
> not.
>
> The ideal is if you send mac80211 patches separated from wl12xx and
> mention the dependency on the wl12xx series, so I'm aware and wait for
> John's integration before taking the wl12xx stuff in.

Sure thing. I'll separate the patches.

Arik

2011-01-31 20:06:50

by Luciano Coelho

[permalink] [raw]
Subject: Re: [PATCH v2 06/10] mac80211: add HW flag for disabling auto link-PS in AP mode

On Mon, 2011-01-31 at 13:11 +0100, Johannes Berg wrote:
> On Mon, 2011-01-31 at 13:49 +0200, Arik Nemtsov wrote:
> > On Mon, Jan 31, 2011 at 13:24, Johannes Berg
> <[email protected]> wrote:
> > > It'd be nicer if you could separate out mac80211 patches, but then
> Luca
> > > needs to hold off merging until John picked up the mac80211 patch.
> >
> > Yea that would complicate matters a bit. Does the ordering of the
> > patches bother you?
>
> It just makes it more complicated for me to pick out the right thing
> --
> I almost missed that there was a mac80211 patch in this huge series.

Yes, I totally agree with Johannes. I prefer that
mac80211/cfg80211/whatever_other_stuff be sent as separate series, so
that there's not confusions about who applies what. If the patches are
in the same series, I always need to ask John to either take them all or
not.

The ideal is if you send mac80211 patches separated from wl12xx and
mention the dependency on the wl12xx series, so I'm aware and wait for
John's integration before taking the wl12xx stuff in.


--
Cheers,
Luca.


2011-01-31 12:11:51

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH v2 06/10] mac80211: add HW flag for disabling auto link-PS in AP mode

On Mon, 2011-01-31 at 13:49 +0200, Arik Nemtsov wrote:
> On Mon, Jan 31, 2011 at 13:24, Johannes Berg <[email protected]> wrote:
> > It'd be nicer if you could separate out mac80211 patches, but then Luca
> > needs to hold off merging until John picked up the mac80211 patch.
>
> Yea that would complicate matters a bit. Does the ordering of the
> patches bother you?

It just makes it more complicated for me to pick out the right thing --
I almost missed that there was a mac80211 patch in this huge series.

> > Also, come to think of it, the ieee80211_sta_block_awake will not be
> > effective I think? Or will it?
>
> Haven't noticed any adverse effects. ap_sta_ps_end() still checks if
> the station is blocked before delivering queued packets.
>
> Also is should be pretty hard to find a driver that uses both mechanisms.
> If it wants to block a station from waking up, it can simply not call
> ieee80211_sta_ps_transition().
>
> Should work though.

Good point, thanks for looking.

johannes


2011-01-31 11:16:41

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 02/10] wl12xx: AP-mode - fix race condition on sta connection

If a sta starts transmitting immediately after authentication, sometimes
the FW deauthenticates it. Fix this by marking the sta "in-connection"
in FW before sending the autentication response.

The "in-connection" entry is automatically removed when connection
succeeds or after a timeout.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/acx.c | 25 +++++++++++++++++++++++++
drivers/net/wireless/wl12xx/acx.h | 9 +++++++++
drivers/net/wireless/wl12xx/tx.c | 19 +++++++++++++++++++
3 files changed, 53 insertions(+), 0 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/acx.c b/drivers/net/wireless/wl12xx/acx.c
index afdc601..feba40a 100644
--- a/drivers/net/wireless/wl12xx/acx.c
+++ b/drivers/net/wireless/wl12xx/acx.c
@@ -1476,3 +1476,28 @@ out:
kfree(acx);
return ret;
}
+
+int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr)
+{
+ struct wl1271_acx_inconnection_sta *acx = NULL;
+ int ret;
+
+ wl1271_debug(DEBUG_ACX, "acx set inconnaction sta %pM", addr);
+
+ acx = kzalloc(sizeof(*acx), GFP_KERNEL);
+ if (!acx)
+ return -ENOMEM;
+
+ memcpy(acx->addr, addr, ETH_ALEN);
+
+ ret = wl1271_cmd_configure(wl, ACX_UPDATE_INCONNECTION_STA_LIST,
+ acx, sizeof(*acx));
+ if (ret < 0) {
+ wl1271_warning("acx set inconnaction sta failed: %d", ret);
+ goto out;
+ }
+
+out:
+ kfree(acx);
+ return ret;
+}
diff --git a/drivers/net/wireless/wl12xx/acx.h b/drivers/net/wireless/wl12xx/acx.h
index 4bbaf04..85f3c95 100644
--- a/drivers/net/wireless/wl12xx/acx.h
+++ b/drivers/net/wireless/wl12xx/acx.h
@@ -1136,6 +1136,13 @@ struct wl1271_acx_max_tx_retry {
u8 padding_1[2];
} __packed;

+struct wl1271_acx_inconnection_sta {
+ struct acx_header header;
+
+ u8 addr[ETH_ALEN];
+ u8 padding1[2];
+} __packed;
+
enum {
ACX_WAKE_UP_CONDITIONS = 0x0002,
ACX_MEM_CFG = 0x0003,
@@ -1194,6 +1201,7 @@ enum {
ACX_COEX_ACTIVITY = 0x0059,
ACX_SET_DCO_ITRIM_PARAMS = 0x0061,
ACX_MAX_TX_FAILURE = 0x0072,
+ ACX_UPDATE_INCONNECTION_STA_LIST = 0x0073,
DOT11_RX_MSDU_LIFE_TIME = 0x1004,
DOT11_CUR_TX_PWR = 0x100D,
DOT11_RX_DOT11_MODE = 0x1012,
@@ -1269,5 +1277,6 @@ int wl1271_acx_set_ba_receiver_session(struct wl1271 *wl, u8 tid_index, u16 ssn,
bool enable);
int wl1271_acx_tsf_info(struct wl1271 *wl, u64 *mactime);
int wl1271_acx_max_tx_retry(struct wl1271 *wl);
+int wl1271_acx_set_inconnection_sta(struct wl1271 *wl, u8 *addr);

#endif /* __WL1271_ACX_H__ */
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index ce9a19b..820f455 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -70,6 +70,22 @@ static void wl1271_free_tx_id(struct wl1271 *wl, int id)
}
}

+static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl,
+ struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr;
+
+ /*
+ * add the station to the known list before transmitting the
+ * authentication response. this way it won't get de-authed by FW
+ * when transmitting too soon.
+ */
+ hdr = (struct ieee80211_hdr *)(skb->data +
+ sizeof(struct wl1271_tx_hw_descr));
+ if (ieee80211_is_auth(hdr->frame_control))
+ wl1271_acx_set_inconnection_sta(wl, hdr->addr1);
+}
+
static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
u32 buf_offset)
{
@@ -238,6 +254,9 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,
if (ret < 0)
return ret;

+ if (wl->bss_type == BSS_TYPE_AP_BSS)
+ wl1271_tx_ap_update_inconnection_sta(wl, skb);
+
wl1271_tx_fill_hdr(wl, skb, extra, info);

/*
--
1.7.1


2011-01-31 11:20:32

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 04/10] mac80211: do not calc frame duration when using HW rate-control

When rate-control is performed in HW, we cannot calculate frame
duration as we do not have the skb transmission rate in SW.

ieee80211_tx_h_calculate_duration() should only be called when
ieee80211_tx_h_rate_ctrl() has been called before to initialize data
in skb->cb. This doesn't happen for drivers with HW rate-control.

Fixes the following warning when operating in AP-mode
in a driver with HW rate-control.

WARNING: at net/mac80211/tx.c:57 ieee80211_duration+0x54/0x1d8 [mac80211]()
Modules linked in: wl1271_sdio wl1271 firmware_class crc7 mac80211 cfg80211
[<c0046090>] (unwind_backtrace+0x0/0x124) from [<c0064c10>] (warn_slowpath_common+0x4c/0x64)
[<c0064c10>] (warn_slowpath_common+0x4c/0x64) from [<c0064c40>] (warn_slowpath_null+0x18/0x1c)
[<c0064c40>] (warn_slowpath_null+0x18/0x1c) from [<bf040e34>] (ieee80211_duration+0x54/0x1d8 [mac80211])
[<bf040e34>] (ieee80211_duration+0x54/0x1d8 [mac80211]) from [<bf04200c>] (invoke_tx_handlers+0xfa0/0x1088 [mac80211])
[<bf04200c>] (invoke_tx_handlers+0xfa0/0x1088 [mac80211]) from [<bf042178>] (ieee80211_tx+0x84/0x248 [mac80211])
[<bf042178>] (ieee80211_tx+0x84/0x248 [mac80211]) from [<bf042f44>] (ieee80211_tx_pending+0x12c/0x278 [mac80211])
[<bf042f44>] (ieee80211_tx_pending+0x12c/0x278 [mac80211]) from [<c0069a9c>] (tasklet_action+0x68/0xbc)
[<c0069a9c>] (tasklet_action+0x68/0xbc) from [<c006a044>] (__do_softirq+0x84/0x114)
[<c006a044>] (__do_softirq+0x84/0x114) from [<c006a1b8>] (do_softirq+0x48/0x54)
[<c006a1b8>] (do_softirq+0x48/0x54) from [<c006a4f8>] (local_bh_enable+0x98/0xcc)
[<c006a4f8>] (local_bh_enable+0x98/0xcc) from [<bf074e60>] (wl1271_rx+0x2e8/0x3a4 [wl1271])
[<bf074e60>] (wl1271_rx+0x2e8/0x3a4 [wl1271]) from [<bf071ae4>] (wl1271_irq_work+0x230/0x310 [wl1271])
[<bf071ae4>] (wl1271_irq_work+0x230/0x310 [wl1271]) from [<c0076864>] (process_one_work+0x208/0x350)
[<c0076864>] (process_one_work+0x208/0x350) from [<c0076e14>] (worker_thread+0x1cc/0x300)
[<c0076e14>] (worker_thread+0x1cc/0x300) from [<c007bb88>] (kthread+0x84/0x8c)
[<c007bb88>] (kthread+0x84/0x8c) from [<c0041494>] (kernel_thread_exit+0x0/0x8)

Signed-off-by: Arik Nemtsov <[email protected]>
---
net/mac80211/tx.c | 3 ++-
1 files changed, 2 insertions(+), 1 deletions(-)

diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index e46c801..8c477a9 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -1394,7 +1394,8 @@ static int invoke_tx_handlers(struct ieee80211_tx_data *tx)
/* handlers after fragment must be aware of tx info fragmentation! */
CALL_TXH(ieee80211_tx_h_stats);
CALL_TXH(ieee80211_tx_h_encrypt);
- CALL_TXH(ieee80211_tx_h_calculate_duration);
+ if (!(tx->local->hw.flags & IEEE80211_HW_HAS_RATE_CONTROL))
+ CALL_TXH(ieee80211_tx_h_calculate_duration);
#undef CALL_TXH

txh_done:
--
1.7.1


2011-01-31 11:24:05

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH v2 06/10] mac80211: add HW flag for disabling auto link-PS in AP mode

It'd be nicer if you could separate out mac80211 patches, but then Luca
needs to hold off merging until John picked up the mac80211 patch.

> + * @IEEE80211_HW_AP_LINK_PS: When operating in AP mode the device
> + * autonomously manages the PS status of connected stations. When
> + * this flag is set mac80211 will not trigger PS mode for connected
> + * stations based on the PM bit of incoming frames.
> + * Use ieee80211_start_ps()/ieee8021_end_ps() to manually configure
> + * the PS mode of connected stations.

You just renamed it.

Also, come to think of it, the ieee80211_sta_block_awake will not be
effective I think? Or will it?

johannes


2011-01-31 11:49:57

by Arik Nemtsov

[permalink] [raw]
Subject: Re: [PATCH v2 06/10] mac80211: add HW flag for disabling auto link-PS in AP mode

On Mon, Jan 31, 2011 at 13:24, Johannes Berg <[email protected]> wrote:
> It'd be nicer if you could separate out mac80211 patches, but then Luca
> needs to hold off merging until John picked up the mac80211 patch.

Yea that would complicate matters a bit. Does the ordering of the
patches bother you?

>
>> + * @IEEE80211_HW_AP_LINK_PS: When operating in AP mode the device
>> + * ? autonomously manages the PS status of connected stations. When
>> + * ? this flag is set mac80211 will not trigger PS mode for connected
>> + * ? stations based on the PM bit of incoming frames.
>> + * ? Use ieee80211_start_ps()/ieee8021_end_ps() to manually configure
>> + * ? the PS mode of connected stations.
>
> You just renamed it.

Yea thanks for that.

>
> Also, come to think of it, the ieee80211_sta_block_awake will not be
> effective I think? Or will it?

Haven't noticed any adverse effects. ap_sta_ps_end() still checks if
the station is blocked before delivering queued packets.

Also is should be pretty hard to find a driver that uses both mechanisms.
If it wants to block a station from waking up, it can simply not call
ieee80211_sta_ps_transition().

Should work though.

Arik

2011-01-31 11:16:49

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 05/10] wl12xx: report invalid TX rate when returning non-TX-ed skbs

Report a TX rate idx of -1 and count 0 when returning untransmitted
skbs to mac80211 using ieee80211_tx_status(). Otherwise mac80211
tries to use the returned (essentially garbage) status.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/tx.c | 11 +++++++++++
1 files changed, 11 insertions(+), 0 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index c658821..71d964c 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -628,10 +628,14 @@ void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid)
struct sk_buff *skb;
int i, total = 0;
unsigned long flags;
+ struct ieee80211_tx_info *info;

for (i = 0; i < NUM_TX_QUEUES; i++) {
while ((skb = skb_dequeue(&wl->links[hlid].tx_queue[i]))) {
wl1271_debug(DEBUG_TX, "link 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);
total++;
}
@@ -649,6 +653,7 @@ void wl1271_tx_reset(struct wl1271 *wl)
{
int i;
struct sk_buff *skb;
+ struct ieee80211_tx_info *info;

/* TX failure */
if (wl->bss_type == BSS_TYPE_AP_BSS) {
@@ -661,6 +666,9 @@ 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);
}
}
@@ -679,6 +687,9 @@ void wl1271_tx_reset(struct wl1271 *wl)
skb = wl->tx_frames[i];
wl1271_free_tx_id(wl, 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);
}
}
--
1.7.1


2011-01-31 11:17:03

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 09/10] wl12xx: AP-mode - count free FW TX blocks per link

Count the number of FW TX blocks allocated per link. We add blocks to a
link counter when allocated for a TX descriptor. We remove blocks
according to counters in fw_status indicating the number of freed blocks
in FW. These counters are polled after each IRQ.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 11 +++++++
drivers/net/wireless/wl12xx/ps.c | 2 -
drivers/net/wireless/wl12xx/tx.c | 53 ++++++++++++++++++---------------
drivers/net/wireless/wl12xx/wl12xx.h | 4 ++
4 files changed, 44 insertions(+), 26 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index 46eb499..f68f1fe 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -553,6 +553,17 @@ static void wl1271_fw_status(struct wl1271 *wl,
if (total)
clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags);

+ if (wl->bss_type == BSS_TYPE_AP_BSS) {
+ for (i = 0; i < AP_MAX_LINKS; i++) {
+ u8 cnt = status->tx_lnk_free_blks[i] -
+ wl->links[i].prev_freed_blks;
+
+ wl->links[i].prev_freed_blks =
+ status->tx_lnk_free_blks[i];
+ wl->links[i].allocated_blks -= cnt;
+ }
+ }
+
/* update the host-chipset time offset */
getnstimeofday(&ts);
wl->time_offset = (timespec_to_ns(&ts) >> 10) -
diff --git a/drivers/net/wireless/wl12xx/ps.c b/drivers/net/wireless/wl12xx/ps.c
index 60a3738..92397ad 100644
--- a/drivers/net/wireless/wl12xx/ps.c
+++ b/drivers/net/wireless/wl12xx/ps.c
@@ -174,5 +174,3 @@ int wl1271_ps_set_mode(struct wl1271 *wl, enum wl1271_cmd_ps_mode mode,

return ret;
}
-
-
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index 71d964c..a91f179 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -108,7 +108,7 @@ u8 wl1271_tx_get_hlid(struct sk_buff *skb)
}

static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
- u32 buf_offset)
+ u32 buf_offset, u8 hlid)
{
struct wl1271_tx_hw_descr *desc;
u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra;
@@ -137,6 +137,9 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,

wl->tx_blocks_available -= total_blocks;

+ if (wl->bss_type == BSS_TYPE_AP_BSS)
+ wl->links[hlid].allocated_blks += total_blocks;
+
ret = 0;

wl1271_debug(DEBUG_TX,
@@ -150,7 +153,8 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
}

static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
- u32 extra, struct ieee80211_tx_info *control)
+ u32 extra, struct ieee80211_tx_info *control,
+ u8 hlid)
{
struct timespec ts;
struct wl1271_tx_hw_descr *desc;
@@ -186,7 +190,7 @@ static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
desc->tid = ac;

if (wl->bss_type != BSS_TYPE_AP_BSS) {
- desc->aid = TX_HW_DEFAULT_AID;
+ desc->aid = hlid;

/* if the packets are destined for AP (have a STA entry)
send them with AP rate policies, otherwise use default
@@ -196,25 +200,17 @@ static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
else
rate_idx = ACX_TX_BASIC_RATE;
} else {
- if (control->control.sta) {
- struct wl1271_station *wl_sta;
-
- wl_sta = (struct wl1271_station *)
- control->control.sta->drv_priv;
- desc->hlid = wl_sta->hlid;
+ desc->hlid = hlid;
+ switch (hlid) {
+ case WL1271_AP_GLOBAL_HLID:
+ rate_idx = ACX_TX_AP_MODE_MGMT_RATE;
+ break;
+ case WL1271_AP_BROADCAST_HLID:
+ rate_idx = ACX_TX_AP_MODE_BCST_RATE;
+ break;
+ default:
rate_idx = ac;
- } else {
- struct ieee80211_hdr *hdr;
-
- hdr = (struct ieee80211_hdr *)
- (skb->data + sizeof(*desc));
- if (ieee80211_is_mgmt(hdr->frame_control)) {
- desc->hlid = WL1271_AP_GLOBAL_HLID;
- rate_idx = ACX_TX_AP_MODE_MGMT_RATE;
- } else {
- desc->hlid = WL1271_AP_BROADCAST_HLID;
- rate_idx = ACX_TX_AP_MODE_BCST_RATE;
- }
+ break;
}
}

@@ -245,6 +241,7 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,
u32 extra = 0;
int ret = 0;
u32 total_len;
+ u8 hlid;

if (!skb)
return -EINVAL;
@@ -271,14 +268,19 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,
}
}

- ret = wl1271_tx_allocate(wl, skb, extra, buf_offset);
+ if (wl->bss_type == BSS_TYPE_AP_BSS)
+ hlid = wl1271_tx_get_hlid(skb);
+ else
+ hlid = TX_HW_DEFAULT_AID;
+
+ ret = wl1271_tx_allocate(wl, skb, extra, buf_offset, hlid);
if (ret < 0)
return ret;

if (wl->bss_type == BSS_TYPE_AP_BSS)
wl1271_tx_ap_update_inconnection_sta(wl, skb);

- wl1271_tx_fill_hdr(wl, skb, extra, info);
+ wl1271_tx_fill_hdr(wl, skb, extra, info, hlid);

/*
* The length of each packet is stored in terms of words. Thus, we must
@@ -657,8 +659,11 @@ void wl1271_tx_reset(struct wl1271 *wl)

/* TX failure */
if (wl->bss_type == BSS_TYPE_AP_BSS) {
- for (i = 0; i < AP_MAX_LINKS; i++)
+ for (i = 0; i < AP_MAX_LINKS; i++) {
wl1271_tx_reset_link_queues(wl, i);
+ wl->links[i].allocated_blks = 0;
+ wl->links[i].prev_freed_blks = 0;
+ }

wl->last_tx_hlid = 0;
} else {
diff --git a/drivers/net/wireless/wl12xx/wl12xx.h b/drivers/net/wireless/wl12xx/wl12xx.h
index 30450e4..d169655 100644
--- a/drivers/net/wireless/wl12xx/wl12xx.h
+++ b/drivers/net/wireless/wl12xx/wl12xx.h
@@ -281,6 +281,10 @@ struct wl1271_ap_key {
struct wl1271_link {
/* AP-mode - TX queue per AC in link */
struct sk_buff_head tx_queue[NUM_TX_QUEUES];
+
+ /* accounting for allocated / available TX blocks in FW */
+ u8 allocated_blks;
+ u8 prev_freed_blks;
};

struct wl1271 {
--
1.7.1


2011-01-31 11:16:39

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 01/10] wl12xx: fix potential race condition with TX queue watermark

Check the conditions for the high/low TX queue watermarks when the
spin-lock is taken. This prevents race conditions as tx_queue_count and
the flag checked are only modified when the spin-lock is taken.

The following race was in mind:
- Queues are almost full and wl1271_op_tx() will stop the queues, but it
doesn't get the spin-lock yet.
- (on another CPU) tx_work_locked() dequeues 15 skbs from this queue and
tx_queue_count is updated to reflect this
- wl1271_op_tx() does not check tx_queue_count after taking the
spin-lock and incorrectly stops the queue.

Signed-off-by: Arik Nemtsov <[email protected]>
---
drivers/net/wireless/wl12xx/main.c | 24 +++++++++++-------------
drivers/net/wireless/wl12xx/tx.c | 2 +-
2 files changed, 12 insertions(+), 14 deletions(-)

diff --git a/drivers/net/wireless/wl12xx/main.c b/drivers/net/wireless/wl12xx/main.c
index dfab21e..cf11bda 100644
--- a/drivers/net/wireless/wl12xx/main.c
+++ b/drivers/net/wireless/wl12xx/main.c
@@ -995,6 +995,17 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
}
#endif
wl->tx_queue_count++;
+
+ /*
+ * The workqueue is slow to process the tx_queue and we need stop
+ * the queue here, otherwise the queue will get too long.
+ */
+ if (wl->tx_queue_count >= WL1271_TX_QUEUE_HIGH_WATERMARK) {
+ wl1271_debug(DEBUG_TX, "op_tx: stopping queues");
+ ieee80211_stop_queues(wl->hw);
+ __set_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags);
+ }
+
spin_unlock_irqrestore(&wl->wl_lock, flags);

/* queue the packet */
@@ -1009,19 +1020,6 @@ static int wl1271_op_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
if (!test_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags))
ieee80211_queue_work(wl->hw, &wl->tx_work);

- /*
- * The workqueue is slow to process the tx_queue and we need stop
- * the queue here, otherwise the queue will get too long.
- */
- if (wl->tx_queue_count >= WL1271_TX_QUEUE_HIGH_WATERMARK) {
- wl1271_debug(DEBUG_TX, "op_tx: stopping queues");
-
- spin_lock_irqsave(&wl->wl_lock, flags);
- ieee80211_stop_queues(wl->hw);
- set_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags);
- spin_unlock_irqrestore(&wl->wl_lock, flags);
- }
-
return NETDEV_TX_OK;
}

diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index 3507c81..ce9a19b 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -288,7 +288,7 @@ static void handle_tx_low_watermark(struct wl1271 *wl)
/* firmware buffer has space, restart queues */
spin_lock_irqsave(&wl->wl_lock, flags);
ieee80211_wake_queues(wl->hw);
- clear_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags);
+ __clear_bit(WL1271_FLAG_TX_QUEUE_STOPPED, &wl->flags);
spin_unlock_irqrestore(&wl->wl_lock, flags);
}
}
--
1.7.1


2011-01-31 11:16:55

by Arik Nemtsov

[permalink] [raw]
Subject: [PATCH v2 06/10] mac80211: add HW flag for disabling auto link-PS in AP mode

When operating in AP mode the wl1271 hardware filters out null-data
packets as well as management packets. This makes it impossible for
mac80211 to monitor the PS mode by using the PM bit of incoming frames.

Implement a HW flag to indicate that mac80211 should ignore the PM bit.
In addition, expose ieee80211_sta_ps_transition() to make low-level
drivers capable of controlling PS-mode.

Signed-off-by: Arik Nemtsov <[email protected]>
---
include/net/mac80211.h | 54 ++++++++++++++++++++++++++++++++++++++++++++++-
net/mac80211/rx.c | 27 +++++++++++++++++++++-
net/mac80211/sta_info.c | 3 +-
net/mac80211/status.c | 4 +++
4 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index d6b0045..0396cec 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1069,6 +1069,13 @@ enum ieee80211_tkip_key_type {
* to decrypt group addressed frames, then IBSS RSN support is still
* possible but software crypto will be used. Advertise the wiphy flag
* only in that case.
+ *
+ * @IEEE80211_HW_AP_LINK_PS: When operating in AP mode the device
+ * autonomously manages the PS status of connected stations. When
+ * this flag is set mac80211 will not trigger PS mode for connected
+ * stations based on the PM bit of incoming frames.
+ * Use ieee80211_start_ps()/ieee8021_end_ps() to manually configure
+ * the PS mode of connected stations.
*/
enum ieee80211_hw_flags {
IEEE80211_HW_HAS_RATE_CONTROL = 1<<0,
@@ -1093,6 +1100,7 @@ enum ieee80211_hw_flags {
IEEE80211_HW_CONNECTION_MONITOR = 1<<19,
IEEE80211_HW_SUPPORTS_CQM_RSSI = 1<<20,
IEEE80211_HW_SUPPORTS_PER_STA_GTK = 1<<21,
+ IEEE80211_HW_AP_LINK_PS = 1<<22,
};

/**
@@ -1701,7 +1709,9 @@ enum ieee80211_ampdu_mlme_action {
* station, AP, IBSS/WDS/mesh peer etc. This callback can sleep.
*
* @sta_notify: Notifies low level driver about power state transition of an
- * associated station, AP, IBSS/WDS/mesh peer etc. Must be atomic.
+ * associated station, AP, IBSS/WDS/mesh peer etc. For a VIF operating
+ * in AP mode, this callback will not be called when the flag
+ * %IEEE80211_HW_AP_LINK_PS is set. Must be atomic.
*
* @conf_tx: Configure TX queue parameters (EDCF (aifs, cw_min, cw_max),
* bursting) for a hardware TX queue.
@@ -2131,6 +2141,48 @@ static inline void ieee80211_rx_ni(struct ieee80211_hw *hw,
local_bh_enable();
}

+/**
+ * ieee80211_sta_ps_transition - PS transition for connected sta
+ *
+ * When operating in AP mode with the %IEEE80211_HW_AP_LINK_PS
+ * flag set, use this function to inform mac80211 about a connected station
+ * entering/leaving PS mode.
+ *
+ * This function may not be called in IRQ context or with softirqs enabled.
+ *
+ * Calls to this function for a single hardware must be synchronized against
+ * each other.
+ *
+ * The function returns -EINVAL when the requested PS mode is already set.
+ *
+ * @sta: currently connected sta
+ * @start: start or stop PS
+ */
+int ieee80211_sta_ps_transition(struct ieee80211_sta *sta, bool start);
+
+/**
+ * ieee80211_sta_ps_transition_ni - PS transition for connected sta
+ * (in process context)
+ *
+ * Like ieee80211_sta_ps_transition() but can be called in process context
+ * (internally disables bottom halves). Concurrent call restriction still
+ * applies.
+ *
+ * @sta: currently connected sta
+ * @start: start or stop PS
+ */
+static inline int ieee80211_sta_ps_transition_ni(struct ieee80211_sta *sta,
+ bool start)
+{
+ int ret;
+
+ local_bh_disable();
+ ret = ieee80211_sta_ps_transition(sta, start);
+ local_bh_enable();
+
+ return ret;
+}
+
/*
* The TX headroom reserved by mac80211 for its own tx_status functions.
* This is enough for the radiotap header.
diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c
index f36d70f..a900145 100644
--- a/net/mac80211/rx.c
+++ b/net/mac80211/rx.c
@@ -1088,7 +1088,8 @@ static void ap_sta_ps_start(struct sta_info *sta)

atomic_inc(&sdata->bss->num_sta_ps);
set_sta_flags(sta, WLAN_STA_PS_STA);
- drv_sta_notify(local, sdata, STA_NOTIFY_SLEEP, &sta->sta);
+ if (!(local->hw.flags & IEEE80211_HW_AP_LINK_PS))
+ drv_sta_notify(local, sdata, STA_NOTIFY_SLEEP, &sta->sta);
#ifdef CONFIG_MAC80211_VERBOSE_PS_DEBUG
printk(KERN_DEBUG "%s: STA %pM aid %d enters power save mode\n",
sdata->name, sta->sta.addr, sta->sta.aid);
@@ -1117,6 +1118,27 @@ static void ap_sta_ps_end(struct sta_info *sta)
ieee80211_sta_ps_deliver_wakeup(sta);
}

+int ieee80211_sta_ps_transition(struct ieee80211_sta *sta, bool start)
+{
+ struct sta_info *sta_inf = container_of(sta, struct sta_info, sta);
+ bool in_ps;
+
+ WARN_ON(!(sta_inf->local->hw.flags & IEEE80211_HW_AP_LINK_PS));
+
+ /* Don't let the same PS state be set twice */
+ in_ps = test_sta_flags(sta_inf, WLAN_STA_PS_STA);
+ if ((start && in_ps) || (!start && !in_ps))
+ return -EINVAL;
+
+ if (start)
+ ap_sta_ps_start(sta_inf);
+ else
+ ap_sta_ps_end(sta_inf);
+
+ return 0;
+}
+EXPORT_SYMBOL(ieee80211_sta_ps_transition);
+
static ieee80211_rx_result debug_noinline
ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
{
@@ -1161,7 +1183,8 @@ ieee80211_rx_h_sta_process(struct ieee80211_rx_data *rx)
* Change STA power saving mode only at the end of a frame
* exchange sequence.
*/
- if (!ieee80211_has_morefrags(hdr->frame_control) &&
+ if (!(sta->local->hw.flags & IEEE80211_HW_AP_LINK_PS) &&
+ !ieee80211_has_morefrags(hdr->frame_control) &&
!(status->rx_flags & IEEE80211_RX_DEFERRED_RELEASE) &&
(rx->sdata->vif.type == NL80211_IFTYPE_AP ||
rx->sdata->vif.type == NL80211_IFTYPE_AP_VLAN)) {
diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c
index c426504..5a11078 100644
--- a/net/mac80211/sta_info.c
+++ b/net/mac80211/sta_info.c
@@ -899,7 +899,8 @@ void ieee80211_sta_ps_deliver_wakeup(struct sta_info *sta)
struct ieee80211_local *local = sdata->local;
int sent, buffered;

- drv_sta_notify(local, sdata, STA_NOTIFY_AWAKE, &sta->sta);
+ if (!(local->hw.flags & IEEE80211_HW_AP_LINK_PS))
+ drv_sta_notify(local, sdata, STA_NOTIFY_AWAKE, &sta->sta);

if (!skb_queue_empty(&sta->ps_tx_buf))
sta_info_clear_tim_bit(sta);
diff --git a/net/mac80211/status.c b/net/mac80211/status.c
index 38a7972..ffb0de9 100644
--- a/net/mac80211/status.c
+++ b/net/mac80211/status.c
@@ -98,6 +98,10 @@ static void ieee80211_handle_filtered_frame(struct ieee80211_local *local,
* (b) always process RX events before TX status events if ordering
* can be unknown, for example with different interrupt status
* bits.
+ * (c) if PS mode transitions are manual (i.e. the flag
+ * %IEEE80211_HW_AP_LINK_PS is set), always process PS state
+ * changes before calling TX status events if ordering can be
+ * unknown.
*/
if (test_sta_flags(sta, WLAN_STA_PS_STA) &&
skb_queue_len(&sta->tx_filtered) < STA_MAX_TX_BUFFER) {
--
1.7.1