summaryrefslogtreecommitdiffstats
path: root/drivers/net/wireless/wl12xx/tx.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/wl12xx/tx.c')
-rw-r--r--drivers/net/wireless/wl12xx/tx.c245
1 files changed, 184 insertions, 61 deletions
diff --git a/drivers/net/wireless/wl12xx/tx.c b/drivers/net/wireless/wl12xx/tx.c
index 5e9ef7d..ca3ab1c 100644
--- a/drivers/net/wireless/wl12xx/tx.c
+++ b/drivers/net/wireless/wl12xx/tx.c
@@ -65,11 +65,36 @@ static int wl1271_alloc_tx_id(struct wl1271 *wl, struct sk_buff *skb)
static void wl1271_free_tx_id(struct wl1271 *wl, int id)
{
if (__test_and_clear_bit(id, wl->tx_frames_map)) {
+ if (unlikely(wl->tx_frames_cnt == ACX_TX_DESCRIPTORS))
+ clear_bit(WL1271_FLAG_FW_TX_BUSY, &wl->flags);
+
wl->tx_frames[id] = NULL;
wl->tx_frames_cnt--;
}
}
+static int wl1271_tx_update_filters(struct wl1271 *wl,
+ struct sk_buff *skb)
+{
+ struct ieee80211_hdr *hdr;
+
+ hdr = (struct ieee80211_hdr *)(skb->data +
+ sizeof(struct wl1271_tx_hw_descr));
+
+ /*
+ * stop bssid-based filtering before transmitting authentication
+ * requests. this way the hw will never drop authentication
+ * responses coming from BSSIDs it isn't familiar with (e.g. on
+ * roaming)
+ */
+ if (!ieee80211_is_auth(hdr->frame_control))
+ return 0;
+
+ wl1271_configure_filters(wl, FIF_OTHER_BSS);
+
+ return wl1271_acx_rx_config(wl, wl->rx_config, wl->rx_filter);
+}
+
static void wl1271_tx_ap_update_inconnection_sta(struct wl1271 *wl,
struct sk_buff *skb)
{
@@ -127,13 +152,29 @@ u8 wl1271_tx_get_hlid(struct sk_buff *skb)
}
}
+static unsigned int wl12xx_calc_packet_alignment(struct wl1271 *wl,
+ unsigned int packet_length)
+{
+ if (wl->quirks & WL12XX_QUIRK_BLOCKSIZE_ALIGNMENT)
+ return ALIGN(packet_length, WL12XX_BUS_BLOCK_SIZE);
+ else
+ return ALIGN(packet_length, WL1271_TX_ALIGN_TO);
+}
+
static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
u32 buf_offset, u8 hlid)
{
struct wl1271_tx_hw_descr *desc;
u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra;
+ u32 len;
u32 total_blocks;
int id, ret = -EBUSY;
+ u32 spare_blocks;
+
+ if (unlikely(wl->quirks & WL12XX_QUIRK_USE_2_SPARE_BLOCKS))
+ spare_blocks = 2;
+ else
+ spare_blocks = 1;
if (buf_offset + total_len > WL1271_AGGR_BUFFER_SIZE)
return -EAGAIN;
@@ -145,17 +186,27 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
/* approximate the number of blocks required for this packet
in the firmware */
- total_blocks = total_len + TX_HW_BLOCK_SIZE - 1;
- total_blocks = total_blocks / TX_HW_BLOCK_SIZE + TX_HW_BLOCK_SPARE;
+ len = wl12xx_calc_packet_alignment(wl, total_len);
+
+ total_blocks = (len + TX_HW_BLOCK_SIZE - 1) / TX_HW_BLOCK_SIZE +
+ spare_blocks;
+
if (total_blocks <= wl->tx_blocks_available) {
desc = (struct wl1271_tx_hw_descr *)skb_push(
skb, total_len - skb->len);
- desc->extra_mem_blocks = TX_HW_BLOCK_SPARE;
- desc->total_mem_blocks = total_blocks;
+ /* HW descriptor fields change between wl127x and wl128x */
+ if (wl->chip.id == CHIP_ID_1283_PG20) {
+ desc->wl128x_mem.total_mem_blocks = total_blocks;
+ } else {
+ desc->wl127x_mem.extra_blocks = spare_blocks;
+ desc->wl127x_mem.total_mem_blocks = total_blocks;
+ }
+
desc->id = id;
wl->tx_blocks_available -= total_blocks;
+ wl->tx_allocated_blocks += total_blocks;
if (wl->bss_type == BSS_TYPE_AP_BSS)
wl->links[hlid].allocated_blks += total_blocks;
@@ -172,13 +223,18 @@ static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra,
return ret;
}
+static bool wl12xx_is_dummy_packet(struct wl1271 *wl, struct sk_buff *skb)
+{
+ return wl->dummy_packet == skb;
+}
+
static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
u32 extra, struct ieee80211_tx_info *control,
u8 hlid)
{
struct timespec ts;
struct wl1271_tx_hw_descr *desc;
- int pad, ac, rate_idx;
+ int aligned_len, ac, rate_idx;
s64 hosttime;
u16 tx_attr;
@@ -202,12 +258,25 @@ static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
else
desc->life_time = cpu_to_le16(TX_HW_AP_MODE_PKT_LIFETIME_TU);
- /* configure the tx attributes */
- tx_attr = wl->session_counter << TX_HW_ATTR_OFST_SESSION_COUNTER;
-
- /* queue (we use same identifiers for tid's and ac's */
+ /* queue */
ac = wl1271_tx_get_queue(skb_get_queue_mapping(skb));
- desc->tid = ac;
+ desc->tid = skb->priority;
+
+ if (wl12xx_is_dummy_packet(wl, skb)) {
+ /*
+ * FW expects the dummy packet to have an invalid session id -
+ * any session id that is different than the one set in the join
+ */
+ tx_attr = ((~wl->session_counter) <<
+ TX_HW_ATTR_OFST_SESSION_COUNTER) &
+ TX_HW_ATTR_SESSION_COUNTER;
+
+ tx_attr |= TX_HW_ATTR_TX_DUMMY_REQ;
+ } else {
+ /* configure the tx attributes */
+ tx_attr =
+ wl->session_counter << TX_HW_ATTR_OFST_SESSION_COUNTER;
+ }
if (wl->bss_type != BSS_TYPE_AP_BSS) {
desc->aid = hlid;
@@ -237,20 +306,37 @@ static void wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb,
tx_attr |= rate_idx << TX_HW_ATTR_OFST_RATE_POLICY;
desc->reserved = 0;
- /* align the length (and store in terms of words) */
- pad = ALIGN(skb->len, WL1271_TX_ALIGN_TO);
- desc->length = cpu_to_le16(pad >> 2);
+ aligned_len = wl12xx_calc_packet_alignment(wl, skb->len);
- /* calculate number of padding bytes */
- pad = pad - skb->len;
- tx_attr |= pad << TX_HW_ATTR_OFST_LAST_WORD_PAD;
+ if (wl->chip.id == CHIP_ID_1283_PG20) {
+ desc->wl128x_mem.extra_bytes = aligned_len - skb->len;
+ desc->length = cpu_to_le16(aligned_len >> 2);
- desc->tx_attr = cpu_to_le16(tx_attr);
+ wl1271_debug(DEBUG_TX, "tx_fill_hdr: hlid: %d "
+ "tx_attr: 0x%x len: %d life: %d mem: %d",
+ desc->hlid, tx_attr,
+ le16_to_cpu(desc->length),
+ le16_to_cpu(desc->life_time),
+ desc->wl128x_mem.total_mem_blocks);
+ } else {
+ int pad;
- wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d hlid: %d "
- "tx_attr: 0x%x len: %d life: %d mem: %d", pad, desc->hlid,
- le16_to_cpu(desc->tx_attr), le16_to_cpu(desc->length),
- le16_to_cpu(desc->life_time), desc->total_mem_blocks);
+ /* Store the aligned length in terms of words */
+ desc->length = cpu_to_le16(aligned_len >> 2);
+
+ /* calculate number of padding bytes */
+ pad = aligned_len - skb->len;
+ tx_attr |= pad << TX_HW_ATTR_OFST_LAST_WORD_PAD;
+
+ wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d hlid: %d "
+ "tx_attr: 0x%x len: %d life: %d mem: %d", pad,
+ desc->hlid, tx_attr,
+ le16_to_cpu(desc->length),
+ le16_to_cpu(desc->life_time),
+ desc->wl127x_mem.total_mem_blocks);
+ }
+
+ desc->tx_attr = cpu_to_le16(tx_attr);
}
/* caller must hold wl->mutex */
@@ -300,19 +386,29 @@ static int wl1271_prepare_tx_frame(struct wl1271 *wl, struct sk_buff *skb,
if (wl->bss_type == BSS_TYPE_AP_BSS) {
wl1271_tx_ap_update_inconnection_sta(wl, skb);
wl1271_tx_regulate_link(wl, hlid);
+ } else {
+ wl1271_tx_update_filters(wl, skb);
}
wl1271_tx_fill_hdr(wl, skb, extra, info, hlid);
/*
- * The length of each packet is stored in terms of words. Thus, we must
- * pad the skb data to make sure its length is aligned.
- * The number of padding bytes is computed and set in wl1271_tx_fill_hdr
+ * The length of each packet is stored in terms of
+ * words. Thus, we must pad the skb data to make sure its
+ * length is aligned. The number of padding bytes is computed
+ * and set in wl1271_tx_fill_hdr.
+ * In special cases, we want to align to a specific block size
+ * (eg. for wl128x with SDIO we align to 256).
*/
- total_len = ALIGN(skb->len, WL1271_TX_ALIGN_TO);
+ total_len = wl12xx_calc_packet_alignment(wl, skb->len);
+
memcpy(wl->aggr_buf + buf_offset, skb->data, skb->len);
memset(wl->aggr_buf + buf_offset + skb->len, 0, total_len - skb->len);
+ /* Revert side effects in the dummy packet skb, so it can be reused */
+ if (wl12xx_is_dummy_packet(wl, skb))
+ skb_pull(skb, sizeof(struct wl1271_tx_hw_descr));
+
return total_len;
}
@@ -425,10 +521,23 @@ out:
static struct sk_buff *wl1271_skb_dequeue(struct wl1271 *wl)
{
+ unsigned long flags;
+ struct sk_buff *skb = NULL;
+
if (wl->bss_type == BSS_TYPE_AP_BSS)
- return wl1271_ap_skb_dequeue(wl);
+ skb = wl1271_ap_skb_dequeue(wl);
+ else
+ skb = wl1271_sta_skb_dequeue(wl);
- return wl1271_sta_skb_dequeue(wl);
+ if (!skb &&
+ test_and_clear_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags)) {
+ skb = wl->dummy_packet;
+ spin_lock_irqsave(&wl->wl_lock, flags);
+ wl->tx_queue_count--;
+ spin_unlock_irqrestore(&wl->wl_lock, flags);
+ }
+
+ return skb;
}
static void wl1271_skb_queue_head(struct wl1271 *wl, struct sk_buff *skb)
@@ -436,7 +545,9 @@ 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));
- if (wl->bss_type == BSS_TYPE_AP_BSS) {
+ if (wl12xx_is_dummy_packet(wl, skb)) {
+ set_bit(WL1271_FLAG_DUMMY_PACKET_PENDING, &wl->flags);
+ } else 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);
@@ -454,22 +565,14 @@ static void wl1271_skb_queue_head(struct wl1271 *wl, struct sk_buff *skb)
void wl1271_tx_work_locked(struct wl1271 *wl)
{
struct sk_buff *skb;
- bool woken_up = false;
u32 buf_offset = 0;
bool sent_packets = false;
int ret;
if (unlikely(wl->state == WL1271_STATE_OFF))
- goto out;
+ return;
while ((skb = wl1271_skb_dequeue(wl))) {
- if (!woken_up) {
- ret = wl1271_ps_elp_wakeup(wl);
- if (ret < 0)
- goto out_ack;
- woken_up = true;
- }
-
ret = wl1271_prepare_tx_frame(wl, skb, buf_offset);
if (ret == -EAGAIN) {
/*
@@ -516,18 +619,22 @@ out_ack:
wl1271_handle_tx_low_watermark(wl);
}
-
-out:
- if (woken_up)
- wl1271_ps_elp_sleep(wl);
}
void wl1271_tx_work(struct work_struct *work)
{
struct wl1271 *wl = container_of(work, struct wl1271, tx_work);
+ int ret;
mutex_lock(&wl->mutex);
+ ret = wl1271_ps_elp_wakeup(wl);
+ if (ret < 0)
+ goto out;
+
wl1271_tx_work_locked(wl);
+
+ wl1271_ps_elp_sleep(wl);
+out:
mutex_unlock(&wl->mutex);
}
@@ -549,6 +656,11 @@ static void wl1271_tx_complete_packet(struct wl1271 *wl,
skb = wl->tx_frames[id];
info = IEEE80211_SKB_CB(skb);
+ if (wl12xx_is_dummy_packet(wl, skb)) {
+ wl1271_free_tx_id(wl, id);
+ return;
+ }
+
/* update the TX status info */
if (result->status == TX_SUCCESS) {
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK))
@@ -657,8 +769,8 @@ void wl1271_tx_reset_link_queues(struct wl1271 *wl, u8 hlid)
wl1271_handle_tx_low_watermark(wl);
}
-/* caller must hold wl->mutex */
-void wl1271_tx_reset(struct wl1271 *wl)
+/* caller must hold wl->mutex and TX must be stopped */
+void wl1271_tx_reset(struct wl1271 *wl, bool reset_tx_queues)
{
int i;
struct sk_buff *skb;
@@ -678,10 +790,13 @@ 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);
+
+ if (!wl12xx_is_dummy_packet(wl, skb)) {
+ info = IEEE80211_SKB_CB(skb);
+ info->status.rates[0].idx = -1;
+ info->status.rates[0].count = 0;
+ ieee80211_tx_status(wl->hw, skb);
+ }
}
}
}
@@ -691,8 +806,10 @@ void wl1271_tx_reset(struct wl1271 *wl)
/*
* Make sure the driver is at a consistent state, in case this
* function is called from a context other than interface removal.
+ * This call will always wake the TX queues.
*/
- wl1271_handle_tx_low_watermark(wl);
+ if (reset_tx_queues)
+ wl1271_handle_tx_low_watermark(wl);
for (i = 0; i < ACX_TX_DESCRIPTORS; i++) {
if (wl->tx_frames[i] == NULL)
@@ -702,21 +819,27 @@ void wl1271_tx_reset(struct wl1271 *wl)
wl1271_free_tx_id(wl, i);
wl1271_debug(DEBUG_TX, "freeing skb 0x%p", skb);
- /* Remove private headers before passing the skb to mac80211 */
- info = IEEE80211_SKB_CB(skb);
- skb_pull(skb, sizeof(struct wl1271_tx_hw_descr));
- if (info->control.hw_key &&
- info->control.hw_key->cipher == WLAN_CIPHER_SUITE_TKIP) {
- int hdrlen = ieee80211_get_hdrlen_from_skb(skb);
- memmove(skb->data + WL1271_TKIP_IV_SPACE, skb->data,
- hdrlen);
- skb_pull(skb, WL1271_TKIP_IV_SPACE);
- }
+ if (!wl12xx_is_dummy_packet(wl, skb)) {
+ /*
+ * Remove private headers before passing the skb to
+ * mac80211
+ */
+ info = IEEE80211_SKB_CB(skb);
+ skb_pull(skb, sizeof(struct wl1271_tx_hw_descr));
+ if (info->control.hw_key &&
+ info->control.hw_key->cipher ==
+ WLAN_CIPHER_SUITE_TKIP) {
+ int hdrlen = ieee80211_get_hdrlen_from_skb(skb);
+ memmove(skb->data + WL1271_TKIP_IV_SPACE,
+ skb->data, hdrlen);
+ skb_pull(skb, WL1271_TKIP_IV_SPACE);
+ }
- info->status.rates[0].idx = -1;
- info->status.rates[0].count = 0;
+ info->status.rates[0].idx = -1;
+ info->status.rates[0].count = 0;
- ieee80211_tx_status(wl->hw, skb);
+ ieee80211_tx_status(wl->hw, skb);
+ }
}
}
OpenPOWER on IntegriCloud