| /* |
| * This file is part of wl1271 |
| * |
| * Copyright (C) 2009 Nokia Corporation |
| * |
| * Contact: Luciano Coelho <luciano.coelho@nokia.com> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA |
| * 02110-1301 USA |
| * |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| |
| #include "wl1271.h" |
| #include "wl1271_spi.h" |
| #include "wl1271_reg.h" |
| #include "wl1271_ps.h" |
| #include "wl1271_tx.h" |
| |
| static int wl1271_tx_id(struct wl1271 *wl, struct sk_buff *skb) |
| { |
| int i; |
| for (i = 0; i < ACX_TX_DESCRIPTORS; i++) |
| if (wl->tx_frames[i] == NULL) { |
| wl->tx_frames[i] = skb; |
| return i; |
| } |
| |
| return -EBUSY; |
| } |
| |
| static int wl1271_tx_allocate(struct wl1271 *wl, struct sk_buff *skb, u32 extra) |
| { |
| struct wl1271_tx_hw_descr *desc; |
| u32 total_len = skb->len + sizeof(struct wl1271_tx_hw_descr) + extra; |
| u32 total_blocks, excluded; |
| int id, ret = -EBUSY; |
| |
| /* allocate free identifier for the packet */ |
| id = wl1271_tx_id(wl, skb); |
| if (id < 0) |
| return id; |
| |
| /* approximate the number of blocks required for this packet |
| in the firmware */ |
| /* FIXME: try to figure out what is done here and make it cleaner */ |
| total_blocks = (total_len + 20) >> TX_HW_BLOCK_SHIFT_DIV; |
| excluded = (total_blocks << 2) + ((total_len + 20) & 0xff) + 34; |
| total_blocks += (excluded > 252) ? 2 : 1; |
| total_blocks += TX_HW_BLOCK_SPARE; |
| |
| 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; |
| desc->id = id; |
| |
| wl->tx_blocks_available -= total_blocks; |
| |
| ret = 0; |
| |
| wl1271_debug(DEBUG_TX, |
| "tx_allocate: size: %d, blocks: %d, id: %d", |
| total_len, total_blocks, id); |
| } else |
| wl->tx_frames[id] = NULL; |
| |
| return ret; |
| } |
| |
| static int wl1271_tx_fill_hdr(struct wl1271 *wl, struct sk_buff *skb, |
| u32 extra, struct ieee80211_tx_info *control) |
| { |
| struct wl1271_tx_hw_descr *desc; |
| int pad; |
| u16 tx_attr; |
| |
| desc = (struct wl1271_tx_hw_descr *) skb->data; |
| |
| /* relocate space for security header */ |
| if (extra) { |
| void *framestart = skb->data + sizeof(*desc); |
| u16 fc = *(u16 *)(framestart + extra); |
| int hdrlen = ieee80211_hdrlen(cpu_to_le16(fc)); |
| memmove(framestart, framestart + extra, hdrlen); |
| } |
| |
| /* configure packet life time */ |
| desc->start_time = cpu_to_le32(jiffies_to_usecs(jiffies) - |
| wl->time_offset); |
| desc->life_time = cpu_to_le16(TX_HW_MGMT_PKT_LIFETIME_TU); |
| |
| /* configure the tx attributes */ |
| tx_attr = wl->session_counter << TX_HW_ATTR_OFST_SESSION_COUNTER; |
| /* FIXME: do we know the packet priority? can we identify mgmt |
| packets, and use max prio for them at least? */ |
| desc->tid = 0; |
| desc->aid = TX_HW_DEFAULT_AID; |
| desc->reserved = 0; |
| |
| /* align the length (and store in terms of words) */ |
| pad = WL1271_TX_ALIGN(skb->len); |
| desc->length = cpu_to_le16(pad >> 2); |
| |
| /* calculate number of padding bytes */ |
| pad = pad - skb->len; |
| tx_attr |= pad << TX_HW_ATTR_OFST_LAST_WORD_PAD; |
| |
| desc->tx_attr = cpu_to_le16(tx_attr); |
| |
| wl1271_debug(DEBUG_TX, "tx_fill_hdr: pad: %d", pad); |
| return 0; |
| } |
| |
| static int wl1271_tx_send_packet(struct wl1271 *wl, struct sk_buff *skb, |
| struct ieee80211_tx_info *control) |
| { |
| |
| struct wl1271_tx_hw_descr *desc; |
| int len; |
| |
| /* FIXME: This is a workaround for getting non-aligned packets. |
| This happens at least with EAPOL packets from the user space. |
| Our DMA requires packets to be aligned on a 4-byte boundary. |
| */ |
| if (unlikely((long)skb->data & 0x03)) { |
| int offset = (4 - (long)skb->data) & 0x03; |
| wl1271_debug(DEBUG_TX, "skb offset %d", offset); |
| |
| /* check whether the current skb can be used */ |
| if (!skb_cloned(skb) && (skb_tailroom(skb) >= offset)) { |
| unsigned char *src = skb->data; |
| |
| /* align the buffer on a 4-byte boundary */ |
| skb_reserve(skb, offset); |
| memmove(skb->data, src, skb->len); |
| } else { |
| wl1271_info("No handler, fixme!"); |
| return -EINVAL; |
| } |
| } |
| |
| len = WL1271_TX_ALIGN(skb->len); |
| |
| /* perform a fixed address block write with the packet */ |
| wl1271_spi_write(wl, WL1271_SLV_MEM_DATA, skb->data, len, true); |
| |
| /* write packet new counter into the write access register */ |
| wl->tx_packets_count++; |
| wl1271_spi_write32(wl, WL1271_HOST_WR_ACCESS, wl->tx_packets_count); |
| |
| desc = (struct wl1271_tx_hw_descr *) skb->data; |
| wl1271_debug(DEBUG_TX, "tx id %u skb 0x%p payload %u (%u words)", |
| desc->id, skb, len, desc->length); |
| |
| return 0; |
| } |
| |
| /* caller must hold wl->mutex */ |
| static int wl1271_tx_frame(struct wl1271 *wl, struct sk_buff *skb) |
| { |
| struct ieee80211_tx_info *info; |
| u32 extra = 0; |
| int ret = 0; |
| u8 idx; |
| |
| if (!skb) |
| return -EINVAL; |
| |
| info = IEEE80211_SKB_CB(skb); |
| |
| if (info->control.hw_key && |
| info->control.hw_key->alg == ALG_TKIP) |
| extra = WL1271_TKIP_IV_SPACE; |
| |
| if (info->control.hw_key) { |
| idx = info->control.hw_key->hw_key_idx; |
| |
| /* FIXME: do we have to do this if we're not using WEP? */ |
| if (unlikely(wl->default_key != idx)) { |
| ret = wl1271_cmd_set_default_wep_key(wl, idx); |
| if (ret < 0) |
| return ret; |
| } |
| } |
| |
| ret = wl1271_tx_allocate(wl, skb, extra); |
| if (ret < 0) |
| return ret; |
| |
| ret = wl1271_tx_fill_hdr(wl, skb, extra, info); |
| if (ret < 0) |
| return ret; |
| |
| ret = wl1271_tx_send_packet(wl, skb, info); |
| if (ret < 0) |
| return ret; |
| |
| return ret; |
| } |
| |
| void wl1271_tx_work(struct work_struct *work) |
| { |
| struct wl1271 *wl = container_of(work, struct wl1271, tx_work); |
| struct sk_buff *skb; |
| bool woken_up = false; |
| int ret; |
| |
| mutex_lock(&wl->mutex); |
| |
| if (unlikely(wl->state == WL1271_STATE_OFF)) |
| goto out; |
| |
| while ((skb = skb_dequeue(&wl->tx_queue))) { |
| if (!woken_up) { |
| ret = wl1271_ps_elp_wakeup(wl, false); |
| if (ret < 0) |
| goto out; |
| woken_up = true; |
| } |
| |
| ret = wl1271_tx_frame(wl, skb); |
| if (ret == -EBUSY) { |
| /* firmware buffer is full, stop queues */ |
| wl1271_debug(DEBUG_TX, "tx_work: fw buffer full, " |
| "stop queues"); |
| ieee80211_stop_queues(wl->hw); |
| wl->tx_queue_stopped = true; |
| skb_queue_head(&wl->tx_queue, skb); |
| goto out; |
| } else if (ret < 0) { |
| dev_kfree_skb(skb); |
| goto out; |
| } else if (wl->tx_queue_stopped) { |
| /* firmware buffer has space, restart queues */ |
| wl1271_debug(DEBUG_TX, |
| "complete_packet: waking queues"); |
| ieee80211_wake_queues(wl->hw); |
| wl->tx_queue_stopped = false; |
| } |
| } |
| |
| out: |
| if (woken_up) |
| wl1271_ps_elp_sleep(wl); |
| |
| mutex_unlock(&wl->mutex); |
| } |
| |
| static void wl1271_tx_complete_packet(struct wl1271 *wl, |
| struct wl1271_tx_hw_res_descr *result) |
| { |
| struct ieee80211_tx_info *info; |
| struct sk_buff *skb; |
| u16 seq; |
| int id = result->id; |
| |
| /* check for id legality */ |
| if (id >= ACX_TX_DESCRIPTORS || wl->tx_frames[id] == NULL) { |
| wl1271_warning("TX result illegal id: %d", id); |
| return; |
| } |
| |
| skb = wl->tx_frames[id]; |
| info = IEEE80211_SKB_CB(skb); |
| |
| /* update packet status */ |
| if (!(info->flags & IEEE80211_TX_CTL_NO_ACK)) { |
| if (result->status == TX_SUCCESS) |
| info->flags |= IEEE80211_TX_STAT_ACK; |
| if (result->status & TX_RETRY_EXCEEDED) { |
| /* FIXME */ |
| /* info->status.excessive_retries = 1; */ |
| wl->stats.excessive_retries++; |
| } |
| } |
| |
| /* FIXME */ |
| /* info->status.retry_count = result->ack_failures; */ |
| wl->stats.retry_count += result->ack_failures; |
| |
| /* update security sequence number */ |
| seq = wl->tx_security_seq_16 + |
| (result->lsb_security_sequence_number - |
| wl->tx_security_last_seq); |
| wl->tx_security_last_seq = result->lsb_security_sequence_number; |
| |
| if (seq < wl->tx_security_seq_16) |
| wl->tx_security_seq_32++; |
| wl->tx_security_seq_16 = seq; |
| |
| /* remove private header from packet */ |
| skb_pull(skb, sizeof(struct wl1271_tx_hw_descr)); |
| |
| /* remove TKIP header space if present */ |
| if (info->control.hw_key && |
| info->control.hw_key->alg == ALG_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); |
| } |
| |
| wl1271_debug(DEBUG_TX, "tx status id %u skb 0x%p failures %u rate 0x%x" |
| " status 0x%x", |
| result->id, skb, result->ack_failures, |
| result->rate_class_index, result->status); |
| |
| /* return the packet to the stack */ |
| ieee80211_tx_status(wl->hw, skb); |
| wl->tx_frames[result->id] = NULL; |
| } |
| |
| /* Called upon reception of a TX complete interrupt */ |
| void wl1271_tx_complete(struct wl1271 *wl, u32 count) |
| { |
| struct wl1271_acx_mem_map *memmap = |
| (struct wl1271_acx_mem_map *)wl->target_mem_map; |
| u32 i; |
| |
| wl1271_debug(DEBUG_TX, "tx_complete received, packets: %d", count); |
| |
| /* read the tx results from the chipset */ |
| wl1271_spi_read(wl, le32_to_cpu(memmap->tx_result), |
| wl->tx_res_if, sizeof(*wl->tx_res_if), false); |
| |
| /* verify that the result buffer is not getting overrun */ |
| if (count > TX_HW_RESULT_QUEUE_LEN) { |
| wl1271_warning("TX result overflow from chipset: %d", count); |
| count = TX_HW_RESULT_QUEUE_LEN; |
| } |
| |
| /* process the results */ |
| for (i = 0; i < count; i++) { |
| struct wl1271_tx_hw_res_descr *result; |
| u8 offset = wl->tx_results_count & TX_HW_RESULT_QUEUE_LEN_MASK; |
| |
| /* process the packet */ |
| result = &(wl->tx_res_if->tx_results_queue[offset]); |
| wl1271_tx_complete_packet(wl, result); |
| |
| wl->tx_results_count++; |
| } |
| |
| /* write host counter to chipset (to ack) */ |
| wl1271_spi_write32(wl, le32_to_cpu(memmap->tx_result) + |
| offsetof(struct wl1271_tx_hw_res_if, |
| tx_result_host_counter), |
| le32_to_cpu(wl->tx_res_if->tx_result_fw_counter)); |
| } |
| |
| /* caller must hold wl->mutex */ |
| void wl1271_tx_flush(struct wl1271 *wl) |
| { |
| int i; |
| struct sk_buff *skb; |
| struct ieee80211_tx_info *info; |
| |
| /* TX failure */ |
| /* control->flags = 0; FIXME */ |
| |
| while ((skb = skb_dequeue(&wl->tx_queue))) { |
| info = IEEE80211_SKB_CB(skb); |
| |
| wl1271_debug(DEBUG_TX, "flushing skb 0x%p", skb); |
| |
| if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) |
| continue; |
| |
| ieee80211_tx_status(wl->hw, skb); |
| } |
| |
| for (i = 0; i < ACX_TX_DESCRIPTORS; i++) |
| if (wl->tx_frames[i] != NULL) { |
| skb = wl->tx_frames[i]; |
| info = IEEE80211_SKB_CB(skb); |
| |
| if (!(info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS)) |
| continue; |
| |
| ieee80211_tx_status(wl->hw, skb); |
| wl->tx_frames[i] = NULL; |
| } |
| } |