#include <net/tso.h>
#include <linux/tcp.h>
#include "iwl-debug.h"
#include "iwl-io.h"
#include "fw/api/commands.h"
#include "fw/api/tx.h"
#include "fw/api/datapath.h"
#include "fw/api/debug.h"
#include "queue/tx.h"
#include "iwl-fh.h"
#include "iwl-scd.h"
#include <linux/dmapool.h>
static void iwl_pcie_gen2_update_byte_tbl(struct iwl_trans *trans,
struct iwl_txq *txq, u16 byte_cnt,
int num_tbs)
{
int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr);
u8 filled_tfd_size, num_fetch_chunks;
u16 len = byte_cnt;
__le16 bc_ent;
if (WARN(idx >= txq->n_window, "%d >= %d\n", idx, txq->n_window))
return;
filled_tfd_size = offsetof(struct iwl_tfh_tfd, tbs) +
num_tbs * sizeof(struct iwl_tfh_tb);
num_fetch_chunks = DIV_ROUND_UP(filled_tfd_size, 64) - 1;
if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) {
struct iwl_gen3_bc_tbl_entry *scd_bc_tbl_gen3 = txq->bc_tbl.addr;
WARN_ON(trans->txqs.bc_table_dword);
WARN_ON(len > 0x3FFF);
bc_ent = cpu_to_le16(len | (num_fetch_chunks << 14));
scd_bc_tbl_gen3[idx].tfd_offset = bc_ent;
} else {
struct iwlagn_scd_bc_tbl *scd_bc_tbl = txq->bc_tbl.addr;
WARN_ON(!trans->txqs.bc_table_dword);
len = DIV_ROUND_UP(len, 4);
WARN_ON(len > 0xFFF);
bc_ent = cpu_to_le16(len | (num_fetch_chunks << 12));
scd_bc_tbl->tfd_offset[idx] = bc_ent;
}
}
void iwl_txq_inc_wr_ptr(struct iwl_trans *trans, struct iwl_txq *txq)
{
lockdep_assert_held(&txq->lock);
IWL_DEBUG_TX(trans, "Q:%d WR: 0x%x\n", txq->id, txq->write_ptr);
iwl_write32(trans, HBUS_TARG_WRPTR, txq->write_ptr | (txq->id << 16));
}
static u8 iwl_txq_gen2_get_num_tbs(struct iwl_trans *trans,
struct iwl_tfh_tfd *tfd)
{
return le16_to_cpu(tfd->num_tbs) & 0x1f;
}
int iwl_txq_gen2_set_tb(struct iwl_trans *trans, struct iwl_tfh_tfd *tfd,
dma_addr_t addr, u16 len)
{
int idx = iwl_txq_gen2_get_num_tbs(trans, tfd);
struct iwl_tfh_tb *tb;
WARN(iwl_txq_crosses_4g_boundary(addr, len),
"possible DMA problem with iova:0x%llx, len:%d\n",
(unsigned long long)addr, len);
if (WARN_ON(idx >= IWL_TFH_NUM_TBS))
return -EINVAL;
tb = &tfd->tbs[idx];
if (le16_to_cpu(tfd->num_tbs) >= trans->txqs.tfd.max_tbs) {
IWL_ERR(trans, "Error can not send more than %d chunks\n",
trans->txqs.tfd.max_tbs);
return -EINVAL;
}
put_unaligned_le64(addr, &tb->addr);
tb->tb_len = cpu_to_le16(len);
tfd->num_tbs = cpu_to_le16(idx + 1);
return idx;
}
static void iwl_txq_set_tfd_invalid_gen2(struct iwl_trans *trans,
struct iwl_tfh_tfd *tfd)
{
tfd->num_tbs = 0;
iwl_txq_gen2_set_tb(trans, tfd, trans->invalid_tx_cmd.dma,
trans->invalid_tx_cmd.size);
}
void iwl_txq_gen2_tfd_unmap(struct iwl_trans *trans, struct iwl_cmd_meta *meta,
struct iwl_tfh_tfd *tfd)
{
int i, num_tbs;
num_tbs = iwl_txq_gen2_get_num_tbs(trans, tfd);
if (num_tbs > trans->txqs.tfd.max_tbs) {
IWL_ERR(trans, "Too many chunks: %i\n", num_tbs);
return;
}
for (i = 1; i < num_tbs; i++) {
if (meta->tbs & BIT(i))
dma_unmap_page(trans->dev,
le64_to_cpu(tfd->tbs[i].addr),
le16_to_cpu(tfd->tbs[i].tb_len),
DMA_TO_DEVICE);
else
dma_unmap_single(trans->dev,
le64_to_cpu(tfd->tbs[i].addr),
le16_to_cpu(tfd->tbs[i].tb_len),
DMA_TO_DEVICE);
}
iwl_txq_set_tfd_invalid_gen2(trans, tfd);
}
void iwl_txq_gen2_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq)
{
int idx = iwl_txq_get_cmd_index(txq, txq->read_ptr);
struct sk_buff *skb;
lockdep_assert_held(&txq->lock);
if (!txq->entries)
return;
iwl_txq_gen2_tfd_unmap(trans, &txq->entries[idx].meta,
iwl_txq_get_tfd(trans, txq, idx));
skb = txq->entries[idx].skb;
if (skb) {
iwl_op_mode_free_skb(trans->op_mode, skb);
txq->entries[idx].skb = NULL;
}
}
static struct page *get_workaround_page(struct iwl_trans *trans,
struct sk_buff *skb)
{
struct page **page_ptr;
struct page *ret;
page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs);
ret = alloc_page(GFP_ATOMIC);
if (!ret)
return NULL;
*(void **)((u8 *)page_address(ret) + PAGE_SIZE - sizeof(void *)) = *page_ptr;
*page_ptr = ret;
return ret;
}
static int iwl_txq_gen2_set_tb_with_wa(struct iwl_trans *trans,
struct sk_buff *skb,
struct iwl_tfh_tfd *tfd,
dma_addr_t phys, void *virt,
u16 len, struct iwl_cmd_meta *meta)
{
dma_addr_t oldphys = phys;
struct page *page;
int ret;
if (unlikely(dma_mapping_error(trans->dev, phys)))
return -ENOMEM;
if (likely(!iwl_txq_crosses_4g_boundary(phys, len))) {
ret = iwl_txq_gen2_set_tb(trans, tfd, phys, len);
if (ret < 0)
goto unmap;
if (meta)
meta->tbs |= BIT(ret);
ret = 0;
goto trace;
}
if (WARN_ON(len > PAGE_SIZE - sizeof(void *))) {
ret = -ENOBUFS;
goto unmap;
}
page = get_workaround_page(trans, skb);
if (!page) {
ret = -ENOMEM;
goto unmap;
}
memcpy(page_address(page), virt, len);
phys = dma_map_single(trans->dev, page_address(page), len,
DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(trans->dev, phys)))
return -ENOMEM;
ret = iwl_txq_gen2_set_tb(trans, tfd, phys, len);
if (ret < 0) {
oldphys = phys;
meta = NULL;
goto unmap;
}
IWL_WARN(trans,
"TB bug workaround: copied %d bytes from 0x%llx to 0x%llx\n",
len, (unsigned long long)oldphys, (unsigned long long)phys);
ret = 0;
unmap:
if (meta)
dma_unmap_page(trans->dev, oldphys, len, DMA_TO_DEVICE);
else
dma_unmap_single(trans->dev, oldphys, len, DMA_TO_DEVICE);
trace:
trace_iwlwifi_dev_tx_tb(trans->dev, skb, virt, phys, len);
return ret;
}
#ifdef CONFIG_INET
struct iwl_tso_hdr_page *get_page_hdr(struct iwl_trans *trans, size_t len,
struct sk_buff *skb)
{
struct iwl_tso_hdr_page *p = this_cpu_ptr(trans->txqs.tso_hdr_page);
struct page **page_ptr;
page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs);
if (WARN_ON(*page_ptr))
return NULL;
if (!p->page)
goto alloc;
if (p->pos + len < (u8 *)page_address(p->page) + PAGE_SIZE -
sizeof(void *))
goto out;
__free_page(p->page);
alloc:
p->page = alloc_page(GFP_ATOMIC);
if (!p->page)
return NULL;
p->pos = page_address(p->page);
*(void **)((u8 *)page_address(p->page) + PAGE_SIZE - sizeof(void *)) = NULL;
out:
*page_ptr = p->page;
get_page(p->page);
return p;
}
#endif
static int iwl_txq_gen2_build_amsdu(struct iwl_trans *trans,
struct sk_buff *skb,
struct iwl_tfh_tfd *tfd, int start_len,
u8 hdr_len,
struct iwl_device_tx_cmd *dev_cmd)
{
#ifdef CONFIG_INET
struct iwl_tx_cmd_gen2 *tx_cmd = (void *)dev_cmd->payload;
struct ieee80211_hdr *hdr = (void *)skb->data;
unsigned int snap_ip_tcp_hdrlen, ip_hdrlen, total_len, hdr_room;
unsigned int mss = skb_shinfo(skb)->gso_size;
u16 length, amsdu_pad;
u8 *start_hdr;
struct iwl_tso_hdr_page *hdr_page;
struct tso_t tso;
trace_iwlwifi_dev_tx(trans->dev, skb, tfd, sizeof(*tfd),
&dev_cmd->hdr, start_len, 0);
ip_hdrlen = skb_transport_header(skb) - skb_network_header(skb);
snap_ip_tcp_hdrlen = 8 + ip_hdrlen + tcp_hdrlen(skb);
total_len = skb->len - snap_ip_tcp_hdrlen - hdr_len;
amsdu_pad = 0;
hdr_room = DIV_ROUND_UP(total_len, mss) *
(3 + snap_ip_tcp_hdrlen + sizeof(struct ethhdr));
hdr_page = get_page_hdr(trans, hdr_room, skb);
if (!hdr_page)
return -ENOMEM;
start_hdr = hdr_page->pos;
skb_pull(skb, hdr_len);
le16_add_cpu(&tx_cmd->len, -snap_ip_tcp_hdrlen);
tso_start(skb, &tso);
while (total_len) {
unsigned int data_left = min_t(unsigned int, mss, total_len);
unsigned int tb_len;
dma_addr_t tb_phys;
u8 *subf_hdrs_start = hdr_page->pos;
total_len -= data_left;
memset(hdr_page->pos, 0, amsdu_pad);
hdr_page->pos += amsdu_pad;
amsdu_pad = (4 - (sizeof(struct ethhdr) + snap_ip_tcp_hdrlen +
data_left)) & 0x3;
ether_addr_copy(hdr_page->pos, ieee80211_get_DA(hdr));
hdr_page->pos += ETH_ALEN;
ether_addr_copy(hdr_page->pos, ieee80211_get_SA(hdr));
hdr_page->pos += ETH_ALEN;
length = snap_ip_tcp_hdrlen + data_left;
*((__be16 *)hdr_page->pos) = cpu_to_be16(length);
hdr_page->pos += sizeof(length);
tso_build_hdr(skb, hdr_page->pos, &tso, data_left, !total_len);
hdr_page->pos += snap_ip_tcp_hdrlen;
tb_len = hdr_page->pos - start_hdr;
tb_phys = dma_map_single(trans->dev, start_hdr,
tb_len, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(trans->dev, tb_phys)))
goto out_err;
iwl_txq_gen2_set_tb(trans, tfd, tb_phys, tb_len);
trace_iwlwifi_dev_tx_tb(trans->dev, skb, start_hdr,
tb_phys, tb_len);
le16_add_cpu(&tx_cmd->len, hdr_page->pos - subf_hdrs_start);
start_hdr = hdr_page->pos;
while (data_left) {
int ret;
tb_len = min_t(unsigned int, tso.size, data_left);
tb_phys = dma_map_single(trans->dev, tso.data,
tb_len, DMA_TO_DEVICE);
ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd,
tb_phys, tso.data,
tb_len, NULL);
if (ret)
goto out_err;
data_left -= tb_len;
tso_build_data(skb, &tso, tb_len);
}
}
skb_push(skb, hdr_len);
return 0;
out_err:
#endif
return -EINVAL;
}
static struct
iwl_tfh_tfd *iwl_txq_gen2_build_tx_amsdu(struct iwl_trans *trans,
struct iwl_txq *txq,
struct iwl_device_tx_cmd *dev_cmd,
struct sk_buff *skb,
struct iwl_cmd_meta *out_meta,
int hdr_len,
int tx_cmd_len)
{
int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr);
struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx);
dma_addr_t tb_phys;
int len;
void *tb1_addr;
tb_phys = iwl_txq_get_first_tb_dma(txq, idx);
iwl_txq_gen2_set_tb(trans, tfd, tb_phys, IWL_FIRST_TB_SIZE);
len = tx_cmd_len + sizeof(struct iwl_cmd_header) + hdr_len -
IWL_FIRST_TB_SIZE;
tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE;
tb_phys = dma_map_single(trans->dev, tb1_addr, len, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(trans->dev, tb_phys)))
goto out_err;
iwl_txq_gen2_set_tb(trans, tfd, tb_phys, len);
if (iwl_txq_gen2_build_amsdu(trans, skb, tfd, len + IWL_FIRST_TB_SIZE,
hdr_len, dev_cmd))
goto out_err;
memcpy(&txq->first_tb_bufs[idx], dev_cmd, IWL_FIRST_TB_SIZE);
return tfd;
out_err:
iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd);
return NULL;
}
static int iwl_txq_gen2_tx_add_frags(struct iwl_trans *trans,
struct sk_buff *skb,
struct iwl_tfh_tfd *tfd,
struct iwl_cmd_meta *out_meta)
{
int i;
for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
const skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
dma_addr_t tb_phys;
unsigned int fragsz = skb_frag_size(frag);
int ret;
if (!fragsz)
continue;
tb_phys = skb_frag_dma_map(trans->dev, frag, 0,
fragsz, DMA_TO_DEVICE);
ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys,
skb_frag_address(frag),
fragsz, out_meta);
if (ret)
return ret;
}
return 0;
}
static struct
iwl_tfh_tfd *iwl_txq_gen2_build_tx(struct iwl_trans *trans,
struct iwl_txq *txq,
struct iwl_device_tx_cmd *dev_cmd,
struct sk_buff *skb,
struct iwl_cmd_meta *out_meta,
int hdr_len,
int tx_cmd_len,
bool pad)
{
int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr);
struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx);
dma_addr_t tb_phys;
int len, tb1_len, tb2_len;
void *tb1_addr;
struct sk_buff *frag;
tb_phys = iwl_txq_get_first_tb_dma(txq, idx);
memcpy(&txq->first_tb_bufs[idx], dev_cmd, IWL_FIRST_TB_SIZE);
iwl_txq_gen2_set_tb(trans, tfd, tb_phys, IWL_FIRST_TB_SIZE);
len = tx_cmd_len + sizeof(struct iwl_cmd_header) + hdr_len -
IWL_FIRST_TB_SIZE;
if (pad)
tb1_len = ALIGN(len, 4);
else
tb1_len = len;
tb1_addr = ((u8 *)&dev_cmd->hdr) + IWL_FIRST_TB_SIZE;
tb_phys = dma_map_single(trans->dev, tb1_addr, tb1_len, DMA_TO_DEVICE);
if (unlikely(dma_mapping_error(trans->dev, tb_phys)))
goto out_err;
iwl_txq_gen2_set_tb(trans, tfd, tb_phys, tb1_len);
trace_iwlwifi_dev_tx(trans->dev, skb, tfd, sizeof(*tfd), &dev_cmd->hdr,
IWL_FIRST_TB_SIZE + tb1_len, hdr_len);
tb2_len = skb_headlen(skb) - hdr_len;
if (tb2_len > 0) {
int ret;
tb_phys = dma_map_single(trans->dev, skb->data + hdr_len,
tb2_len, DMA_TO_DEVICE);
ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys,
skb->data + hdr_len, tb2_len,
NULL);
if (ret)
goto out_err;
}
if (iwl_txq_gen2_tx_add_frags(trans, skb, tfd, out_meta))
goto out_err;
skb_walk_frags(skb, frag) {
int ret;
tb_phys = dma_map_single(trans->dev, frag->data,
skb_headlen(frag), DMA_TO_DEVICE);
ret = iwl_txq_gen2_set_tb_with_wa(trans, skb, tfd, tb_phys,
frag->data,
skb_headlen(frag), NULL);
if (ret)
goto out_err;
if (iwl_txq_gen2_tx_add_frags(trans, frag, tfd, out_meta))
goto out_err;
}
return tfd;
out_err:
iwl_txq_gen2_tfd_unmap(trans, out_meta, tfd);
return NULL;
}
static
struct iwl_tfh_tfd *iwl_txq_gen2_build_tfd(struct iwl_trans *trans,
struct iwl_txq *txq,
struct iwl_device_tx_cmd *dev_cmd,
struct sk_buff *skb,
struct iwl_cmd_meta *out_meta)
{
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
int idx = iwl_txq_get_cmd_index(txq, txq->write_ptr);
struct iwl_tfh_tfd *tfd = iwl_txq_get_tfd(trans, txq, idx);
int len, hdr_len;
bool amsdu;
BUILD_BUG_ON(sizeof(struct iwl_tx_cmd_gen2) < IWL_FIRST_TB_SIZE);
BUILD_BUG_ON(sizeof(struct iwl_cmd_header) +
offsetofend(struct iwl_tx_cmd_gen2, dram_info) >
IWL_FIRST_TB_SIZE);
BUILD_BUG_ON(sizeof(struct iwl_tx_cmd_gen3) < IWL_FIRST_TB_SIZE);
BUILD_BUG_ON(sizeof(struct iwl_cmd_header) +
offsetofend(struct iwl_tx_cmd_gen3, dram_info) >
IWL_FIRST_TB_SIZE);
memset(tfd, 0, sizeof(*tfd));
if (trans->trans_cfg->device_family < IWL_DEVICE_FAMILY_AX210)
len = sizeof(struct iwl_tx_cmd_gen2);
else
len = sizeof(struct iwl_tx_cmd_gen3);
amsdu = ieee80211_is_data_qos(hdr->frame_control) &&
(*ieee80211_get_qos_ctl(hdr) &
IEEE80211_QOS_CTL_A_MSDU_PRESENT);
hdr_len = ieee80211_hdrlen(hdr->frame_control);
if (amsdu && skb_shinfo(skb)->gso_size)
return iwl_txq_gen2_build_tx_amsdu(trans, txq, dev_cmd, skb,
out_meta, hdr_len, len);
return iwl_txq_gen2_build_tx(trans, txq, dev_cmd, skb, out_meta,
hdr_len, len, !amsdu);
}
int iwl_txq_space(struct iwl_trans *trans, const struct iwl_txq *q)
{
unsigned int max;
unsigned int used;
if (q->n_window < trans->trans_cfg->base_params->max_tfd_queue_size)
max = q->n_window;
else
max = trans->trans_cfg->base_params->max_tfd_queue_size - 1;
used = (q->write_ptr - q->read_ptr) &
(trans->trans_cfg->base_params->max_tfd_queue_size - 1);
if (WARN_ON(used > max))
return 0;
return max - used;
}
int iwl_txq_gen2_tx(struct iwl_trans *trans, struct sk_buff *skb,
struct iwl_device_tx_cmd *dev_cmd, int txq_id)
{
struct iwl_cmd_meta *out_meta;
struct iwl_txq *txq = trans->txqs.txq[txq_id];
u16 cmd_len;
int idx;
void *tfd;
if (WARN_ONCE(txq_id >= IWL_MAX_TVQM_QUEUES,
"queue %d out of range", txq_id))
return -EINVAL;
if (WARN_ONCE(!test_bit(txq_id, trans->txqs.queue_used),
"TX on unused queue %d\n", txq_id))
return -EINVAL;
if (skb_is_nonlinear(skb) &&
skb_shinfo(skb)->nr_frags > IWL_TRANS_MAX_FRAGS(trans) &&
__skb_linearize(skb))
return -ENOMEM;
spin_lock(&txq->lock);
if (iwl_txq_space(trans, txq) < txq->high_mark) {
iwl_txq_stop(trans, txq);
if (unlikely(iwl_txq_space(trans, txq) < 3)) {
struct iwl_device_tx_cmd **dev_cmd_ptr;
dev_cmd_ptr = (void *)((u8 *)skb->cb +
trans->txqs.dev_cmd_offs);
*dev_cmd_ptr = dev_cmd;
__skb_queue_tail(&txq->overflow_q, skb);
spin_unlock(&txq->lock);
return 0;
}
}
idx = iwl_txq_get_cmd_index(txq, txq->write_ptr);
txq->entries[idx].skb = skb;
txq->entries[idx].cmd = dev_cmd;
dev_cmd->hdr.sequence =
cpu_to_le16((u16)(QUEUE_TO_SEQ(txq_id) |
INDEX_TO_SEQ(idx)));
out_meta = &txq->entries[idx].meta;
out_meta->flags = 0;
tfd = iwl_txq_gen2_build_tfd(trans, txq, dev_cmd, skb, out_meta);
if (!tfd) {
spin_unlock(&txq->lock);
return -1;
}
if (trans->trans_cfg->device_family >= IWL_DEVICE_FAMILY_AX210) {
struct iwl_tx_cmd_gen3 *tx_cmd_gen3 =
(void *)dev_cmd->payload;
cmd_len = le16_to_cpu(tx_cmd_gen3->len);
} else {
struct iwl_tx_cmd_gen2 *tx_cmd_gen2 =
(void *)dev_cmd->payload;
cmd_len = le16_to_cpu(tx_cmd_gen2->len);
}
iwl_pcie_gen2_update_byte_tbl(trans, txq, cmd_len,
iwl_txq_gen2_get_num_tbs(trans, tfd));
if (txq->read_ptr == txq->write_ptr && txq->wd_timeout)
mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout);
txq->write_ptr = iwl_txq_inc_wrap(trans, txq->write_ptr);
iwl_txq_inc_wr_ptr(trans, txq);
spin_unlock(&txq->lock);
return 0;
}
void iwl_txq_gen2_unmap(struct iwl_trans *trans, int txq_id)
{
struct iwl_txq *txq = trans->txqs.txq[txq_id];
spin_lock_bh(&txq->lock);
while (txq->write_ptr != txq->read_ptr) {
IWL_DEBUG_TX_REPLY(trans, "Q %d Free %d\n",
txq_id, txq->read_ptr);
if (txq_id != trans->txqs.cmd.q_id) {
int idx = iwl_txq_get_cmd_index(txq, txq->read_ptr);
struct sk_buff *skb = txq->entries[idx].skb;
if (!WARN_ON_ONCE(!skb))
iwl_txq_free_tso_page(trans, skb);
}
iwl_txq_gen2_free_tfd(trans, txq);
txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr);
}
while (!skb_queue_empty(&txq->overflow_q)) {
struct sk_buff *skb = __skb_dequeue(&txq->overflow_q);
iwl_op_mode_free_skb(trans->op_mode, skb);
}
spin_unlock_bh(&txq->lock);
iwl_wake_queue(trans, txq);
}
static void iwl_txq_gen2_free_memory(struct iwl_trans *trans,
struct iwl_txq *txq)
{
struct device *dev = trans->dev;
if (txq->tfds) {
dma_free_coherent(dev,
trans->txqs.tfd.size * txq->n_window,
txq->tfds, txq->dma_addr);
dma_free_coherent(dev,
sizeof(*txq->first_tb_bufs) * txq->n_window,
txq->first_tb_bufs, txq->first_tb_dma);
}
kfree(txq->entries);
if (txq->bc_tbl.addr)
dma_pool_free(trans->txqs.bc_pool,
txq->bc_tbl.addr, txq->bc_tbl.dma);
kfree(txq);
}
static void iwl_txq_gen2_free(struct iwl_trans *trans, int txq_id)
{
struct iwl_txq *txq;
int i;
if (WARN_ONCE(txq_id >= IWL_MAX_TVQM_QUEUES,
"queue %d out of range", txq_id))
return;
txq = trans->txqs.txq[txq_id];
if (WARN_ON(!txq))
return;
iwl_txq_gen2_unmap(trans, txq_id);
if (txq_id == trans->txqs.cmd.q_id)
for (i = 0; i < txq->n_window; i++) {
kfree_sensitive(txq->entries[i].cmd);
kfree_sensitive(txq->entries[i].free_buf);
}
del_timer_sync(&txq->stuck_timer);
iwl_txq_gen2_free_memory(trans, txq);
trans->txqs.txq[txq_id] = NULL;
clear_bit(txq_id, trans->txqs.queue_used);
}
static int iwl_queue_init(struct iwl_txq *q, int slots_num)
{
q->n_window = slots_num;
if (WARN_ON(!is_power_of_2(slots_num)))
return -EINVAL;
q->low_mark = q->n_window / 4;
if (q->low_mark < 4)
q->low_mark = 4;
q->high_mark = q->n_window / 8;
if (q->high_mark < 2)
q->high_mark = 2;
q->write_ptr = 0;
q->read_ptr = 0;
return 0;
}
int iwl_txq_init(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num,
bool cmd_queue)
{
int ret;
u32 tfd_queue_max_size =
trans->trans_cfg->base_params->max_tfd_queue_size;
txq->need_update = false;
if (WARN_ONCE(tfd_queue_max_size & (tfd_queue_max_size - 1),
"Max tfd queue size must be a power of two, but is %d",
tfd_queue_max_size))
return -EINVAL;
ret = iwl_queue_init(txq, slots_num);
if (ret)
return ret;
spin_lock_init(&txq->lock);
if (cmd_queue) {
static struct lock_class_key iwl_txq_cmd_queue_lock_class;
lockdep_set_class(&txq->lock, &iwl_txq_cmd_queue_lock_class);
}
__skb_queue_head_init(&txq->overflow_q);
return 0;
}
void iwl_txq_free_tso_page(struct iwl_trans *trans, struct sk_buff *skb)
{
struct page **page_ptr;
struct page *next;
page_ptr = (void *)((u8 *)skb->cb + trans->txqs.page_offs);
next = *page_ptr;
*page_ptr = NULL;
while (next) {
struct page *tmp = next;
next = *(void **)((u8 *)page_address(next) + PAGE_SIZE -
sizeof(void *));
__free_page(tmp);
}
}
void iwl_txq_log_scd_error(struct iwl_trans *trans, struct iwl_txq *txq)
{
u32 txq_id = txq->id;
u32 status;
bool active;
u8 fifo;
if (trans->trans_cfg->gen2) {
IWL_ERR(trans, "Queue %d is stuck %d %d\n", txq_id,
txq->read_ptr, txq->write_ptr);
return;
}
status = iwl_read_prph(trans, SCD_QUEUE_STATUS_BITS(txq_id));
fifo = (status >> SCD_QUEUE_STTS_REG_POS_TXF) & 0x7;
active = !!(status & BIT(SCD_QUEUE_STTS_REG_POS_ACTIVE));
IWL_ERR(trans,
"Queue %d is %sactive on fifo %d and stuck for %u ms. SW [%d, %d] HW [%d, %d] FH TRB=0x0%x\n",
txq_id, active ? "" : "in", fifo,
jiffies_to_msecs(txq->wd_timeout),
txq->read_ptr, txq->write_ptr,
iwl_read_prph(trans, SCD_QUEUE_RDPTR(txq_id)) &
(trans->trans_cfg->base_params->max_tfd_queue_size - 1),
iwl_read_prph(trans, SCD_QUEUE_WRPTR(txq_id)) &
(trans->trans_cfg->base_params->max_tfd_queue_size - 1),
iwl_read_direct32(trans, FH_TX_TRB_REG(fifo)));
}
static void iwl_txq_stuck_timer(struct timer_list *t)
{
struct iwl_txq *txq = from_timer(txq, t, stuck_timer);
struct iwl_trans *trans = txq->trans;
spin_lock(&txq->lock);
if (txq->read_ptr == txq->write_ptr) {
spin_unlock(&txq->lock);
return;
}
spin_unlock(&txq->lock);
iwl_txq_log_scd_error(trans, txq);
iwl_force_nmi(trans);
}
static void iwl_txq_set_tfd_invalid_gen1(struct iwl_trans *trans,
struct iwl_tfd *tfd)
{
tfd->num_tbs = 0;
iwl_pcie_gen1_tfd_set_tb(trans, tfd, 0, trans->invalid_tx_cmd.dma,
trans->invalid_tx_cmd.size);
}
int iwl_txq_alloc(struct iwl_trans *trans, struct iwl_txq *txq, int slots_num,
bool cmd_queue)
{
size_t num_entries = trans->trans_cfg->gen2 ?
slots_num : trans->trans_cfg->base_params->max_tfd_queue_size;
size_t tfd_sz;
size_t tb0_buf_sz;
int i;
if (WARN_ONCE(slots_num <= 0, "Invalid slots num:%d\n", slots_num))
return -EINVAL;
if (WARN_ON(txq->entries || txq->tfds))
return -EINVAL;
tfd_sz = trans->txqs.tfd.size * num_entries;
timer_setup(&txq->stuck_timer, iwl_txq_stuck_timer, 0);
txq->trans = trans;
txq->n_window = slots_num;
txq->entries = kcalloc(slots_num,
sizeof(struct iwl_pcie_txq_entry),
GFP_KERNEL);
if (!txq->entries)
goto error;
if (cmd_queue)
for (i = 0; i < slots_num; i++) {
txq->entries[i].cmd =
kmalloc(sizeof(struct iwl_device_cmd),
GFP_KERNEL);
if (!txq->entries[i].cmd)
goto error;
}
txq->tfds = dma_alloc_coherent(trans->dev, tfd_sz,
&txq->dma_addr, GFP_KERNEL);
if (!txq->tfds)
goto error;
BUILD_BUG_ON(sizeof(*txq->first_tb_bufs) != IWL_FIRST_TB_SIZE_ALIGN);
tb0_buf_sz = sizeof(*txq->first_tb_bufs) * slots_num;
txq->first_tb_bufs = dma_alloc_coherent(trans->dev, tb0_buf_sz,
&txq->first_tb_dma,
GFP_KERNEL);
if (!txq->first_tb_bufs)
goto err_free_tfds;
for (i = 0; i < num_entries; i++) {
void *tfd = iwl_txq_get_tfd(trans, txq, i);
if (trans->trans_cfg->gen2)
iwl_txq_set_tfd_invalid_gen2(trans, tfd);
else
iwl_txq_set_tfd_invalid_gen1(trans, tfd);
}
return 0;
err_free_tfds:
dma_free_coherent(trans->dev, tfd_sz, txq->tfds, txq->dma_addr);
txq->tfds = NULL;
error:
if (txq->entries && cmd_queue)
for (i = 0; i < slots_num; i++)
kfree(txq->entries[i].cmd);
kfree(txq->entries);
txq->entries = NULL;
return -ENOMEM;
}
static struct iwl_txq *
iwl_txq_dyn_alloc_dma(struct iwl_trans *trans, int size, unsigned int timeout)
{
size_t bc_tbl_size, bc_tbl_entries;
struct iwl_txq *txq;
int ret;
WARN_ON(!trans->txqs.bc_tbl_size);
bc_tbl_size = trans->txqs.bc_tbl_size;
bc_tbl_entries = bc_tbl_size / sizeof(u16);
if (WARN_ON(size > bc_tbl_entries))
return ERR_PTR(-EINVAL);
txq = kzalloc(sizeof(*txq), GFP_KERNEL);
if (!txq)
return ERR_PTR(-ENOMEM);
txq->bc_tbl.addr = dma_pool_alloc(trans->txqs.bc_pool, GFP_KERNEL,
&txq->bc_tbl.dma);
if (!txq->bc_tbl.addr) {
IWL_ERR(trans, "Scheduler BC Table allocation failed\n");
kfree(txq);
return ERR_PTR(-ENOMEM);
}
ret = iwl_txq_alloc(trans, txq, size, false);
if (ret) {
IWL_ERR(trans, "Tx queue alloc failed\n");
goto error;
}
ret = iwl_txq_init(trans, txq, size, false);
if (ret) {
IWL_ERR(trans, "Tx queue init failed\n");
goto error;
}
txq->wd_timeout = msecs_to_jiffies(timeout);
return txq;
error:
iwl_txq_gen2_free_memory(trans, txq);
return ERR_PTR(ret);
}
static int iwl_txq_alloc_response(struct iwl_trans *trans, struct iwl_txq *txq,
struct iwl_host_cmd *hcmd)
{
struct iwl_tx_queue_cfg_rsp *rsp;
int ret, qid;
u32 wr_ptr;
if (WARN_ON(iwl_rx_packet_payload_len(hcmd->resp_pkt) !=
sizeof(*rsp))) {
ret = -EINVAL;
goto error_free_resp;
}
rsp = (void *)hcmd->resp_pkt->data;
qid = le16_to_cpu(rsp->queue_number);
wr_ptr = le16_to_cpu(rsp->write_pointer);
if (qid >= ARRAY_SIZE(trans->txqs.txq)) {
WARN_ONCE(1, "queue index %d unsupported", qid);
ret = -EIO;
goto error_free_resp;
}
if (test_and_set_bit(qid, trans->txqs.queue_used)) {
WARN_ONCE(1, "queue %d already used", qid);
ret = -EIO;
goto error_free_resp;
}
if (WARN_ONCE(trans->txqs.txq[qid],
"queue %d already allocated\n", qid)) {
ret = -EIO;
goto error_free_resp;
}
txq->id = qid;
trans->txqs.txq[qid] = txq;
wr_ptr &= (trans->trans_cfg->base_params->max_tfd_queue_size - 1);
txq->read_ptr = wr_ptr;
txq->write_ptr = wr_ptr;
IWL_DEBUG_TX_QUEUES(trans, "Activate queue %d\n", qid);
iwl_free_resp(hcmd);
return qid;
error_free_resp:
iwl_free_resp(hcmd);
iwl_txq_gen2_free_memory(trans, txq);
return ret;
}
int iwl_txq_dyn_alloc(struct iwl_trans *trans, u32 flags, u32 sta_mask,
u8 tid, int size, unsigned int timeout)
{
struct iwl_txq *txq;
union {
struct iwl_tx_queue_cfg_cmd old;
struct iwl_scd_queue_cfg_cmd new;
} cmd;
struct iwl_host_cmd hcmd = {
.flags = CMD_WANT_SKB,
};
int ret;
if (trans->trans_cfg->device_family == IWL_DEVICE_FAMILY_BZ &&
trans->hw_rev_step == SILICON_A_STEP)
size = 4096;
txq = iwl_txq_dyn_alloc_dma(trans, size, timeout);
if (IS_ERR(txq))
return PTR_ERR(txq);
if (trans->txqs.queue_alloc_cmd_ver == 0) {
memset(&cmd.old, 0, sizeof(cmd.old));
cmd.old.tfdq_addr = cpu_to_le64(txq->dma_addr);
cmd.old.byte_cnt_addr = cpu_to_le64(txq->bc_tbl.dma);
cmd.old.cb_size = cpu_to_le32(TFD_QUEUE_CB_SIZE(size));
cmd.old.flags = cpu_to_le16(flags | TX_QUEUE_CFG_ENABLE_QUEUE);
cmd.old.tid = tid;
if (hweight32(sta_mask) != 1) {
ret = -EINVAL;
goto error;
}
cmd.old.sta_id = ffs(sta_mask) - 1;
hcmd.id = SCD_QUEUE_CFG;
hcmd.len[0] = sizeof(cmd.old);
hcmd.data[0] = &cmd.old;
} else if (trans->txqs.queue_alloc_cmd_ver == 3) {
memset(&cmd.new, 0, sizeof(cmd.new));
cmd.new.operation = cpu_to_le32(IWL_SCD_QUEUE_ADD);
cmd.new.u.add.tfdq_dram_addr = cpu_to_le64(txq->dma_addr);
cmd.new.u.add.bc_dram_addr = cpu_to_le64(txq->bc_tbl.dma);
cmd.new.u.add.cb_size = cpu_to_le32(TFD_QUEUE_CB_SIZE(size));
cmd.new.u.add.flags = cpu_to_le32(flags);
cmd.new.u.add.sta_mask = cpu_to_le32(sta_mask);
cmd.new.u.add.tid = tid;
hcmd.id = WIDE_ID(DATA_PATH_GROUP, SCD_QUEUE_CONFIG_CMD);
hcmd.len[0] = sizeof(cmd.new);
hcmd.data[0] = &cmd.new;
} else {
ret = -EOPNOTSUPP;
goto error;
}
ret = iwl_trans_send_cmd(trans, &hcmd);
if (ret)
goto error;
return iwl_txq_alloc_response(trans, txq, &hcmd);
error:
iwl_txq_gen2_free_memory(trans, txq);
return ret;
}
void iwl_txq_dyn_free(struct iwl_trans *trans, int queue)
{
if (WARN(queue >= IWL_MAX_TVQM_QUEUES,
"queue %d out of range", queue))
return;
if (!test_and_clear_bit(queue, trans->txqs.queue_used)) {
WARN_ONCE(test_bit(STATUS_DEVICE_ENABLED, &trans->status),
"queue %d not used", queue);
return;
}
iwl_txq_gen2_free(trans, queue);
IWL_DEBUG_TX_QUEUES(trans, "Deactivate queue %d\n", queue);
}
void iwl_txq_gen2_tx_free(struct iwl_trans *trans)
{
int i;
memset(trans->txqs.queue_used, 0, sizeof(trans->txqs.queue_used));
for (i = 0; i < ARRAY_SIZE(trans->txqs.txq); i++) {
if (!trans->txqs.txq[i])
continue;
iwl_txq_gen2_free(trans, i);
}
}
int iwl_txq_gen2_init(struct iwl_trans *trans, int txq_id, int queue_size)
{
struct iwl_txq *queue;
int ret;
if (!trans->txqs.txq[txq_id]) {
queue = kzalloc(sizeof(*queue), GFP_KERNEL);
if (!queue) {
IWL_ERR(trans, "Not enough memory for tx queue\n");
return -ENOMEM;
}
trans->txqs.txq[txq_id] = queue;
ret = iwl_txq_alloc(trans, queue, queue_size, true);
if (ret) {
IWL_ERR(trans, "Tx %d queue init failed\n", txq_id);
goto error;
}
} else {
queue = trans->txqs.txq[txq_id];
}
ret = iwl_txq_init(trans, queue, queue_size,
(txq_id == trans->txqs.cmd.q_id));
if (ret) {
IWL_ERR(trans, "Tx %d queue alloc failed\n", txq_id);
goto error;
}
trans->txqs.txq[txq_id]->id = txq_id;
set_bit(txq_id, trans->txqs.queue_used);
return 0;
error:
iwl_txq_gen2_tx_free(trans);
return ret;
}
static inline dma_addr_t iwl_txq_gen1_tfd_tb_get_addr(struct iwl_trans *trans,
struct iwl_tfd *tfd, u8 idx)
{
struct iwl_tfd_tb *tb = &tfd->tbs[idx];
dma_addr_t addr;
dma_addr_t hi_len;
addr = get_unaligned_le32(&tb->lo);
if (sizeof(dma_addr_t) <= sizeof(u32))
return addr;
hi_len = le16_to_cpu(tb->hi_n_len) & 0xF;
return addr | ((hi_len << 16) << 16);
}
void iwl_txq_gen1_tfd_unmap(struct iwl_trans *trans,
struct iwl_cmd_meta *meta,
struct iwl_txq *txq, int index)
{
int i, num_tbs;
struct iwl_tfd *tfd = iwl_txq_get_tfd(trans, txq, index);
num_tbs = iwl_txq_gen1_tfd_get_num_tbs(trans, tfd);
if (num_tbs > trans->txqs.tfd.max_tbs) {
IWL_ERR(trans, "Too many chunks: %i\n", num_tbs);
return;
}
for (i = 1; i < num_tbs; i++) {
if (meta->tbs & BIT(i))
dma_unmap_page(trans->dev,
iwl_txq_gen1_tfd_tb_get_addr(trans,
tfd, i),
iwl_txq_gen1_tfd_tb_get_len(trans,
tfd, i),
DMA_TO_DEVICE);
else
dma_unmap_single(trans->dev,
iwl_txq_gen1_tfd_tb_get_addr(trans,
tfd, i),
iwl_txq_gen1_tfd_tb_get_len(trans,
tfd, i),
DMA_TO_DEVICE);
}
meta->tbs = 0;
iwl_txq_set_tfd_invalid_gen1(trans, tfd);
}
#define IWL_TX_CRC_SIZE 4
#define IWL_TX_DELIMITER_SIZE 4
void iwl_txq_gen1_update_byte_cnt_tbl(struct iwl_trans *trans,
struct iwl_txq *txq, u16 byte_cnt,
int num_tbs)
{
struct iwlagn_scd_bc_tbl *scd_bc_tbl;
int write_ptr = txq->write_ptr;
int txq_id = txq->id;
u8 sec_ctl = 0;
u16 len = byte_cnt + IWL_TX_CRC_SIZE + IWL_TX_DELIMITER_SIZE;
__le16 bc_ent;
struct iwl_device_tx_cmd *dev_cmd = txq->entries[txq->write_ptr].cmd;
struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload;
u8 sta_id = tx_cmd->sta_id;
scd_bc_tbl = trans->txqs.scd_bc_tbls.addr;
sec_ctl = tx_cmd->sec_ctl;
switch (sec_ctl & TX_CMD_SEC_MSK) {
case TX_CMD_SEC_CCM:
len += IEEE80211_CCMP_MIC_LEN;
break;
case TX_CMD_SEC_TKIP:
len += IEEE80211_TKIP_ICV_LEN;
break;
case TX_CMD_SEC_WEP:
len += IEEE80211_WEP_IV_LEN + IEEE80211_WEP_ICV_LEN;
break;
}
if (trans->txqs.bc_table_dword)
len = DIV_ROUND_UP(len, 4);
if (WARN_ON(len > 0xFFF || write_ptr >= TFD_QUEUE_SIZE_MAX))
return;
bc_ent = cpu_to_le16(len | (sta_id << 12));
scd_bc_tbl[txq_id].tfd_offset[write_ptr] = bc_ent;
if (write_ptr < TFD_QUEUE_SIZE_BC_DUP)
scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + write_ptr] =
bc_ent;
}
void iwl_txq_gen1_inval_byte_cnt_tbl(struct iwl_trans *trans,
struct iwl_txq *txq)
{
struct iwlagn_scd_bc_tbl *scd_bc_tbl = trans->txqs.scd_bc_tbls.addr;
int txq_id = txq->id;
int read_ptr = txq->read_ptr;
u8 sta_id = 0;
__le16 bc_ent;
struct iwl_device_tx_cmd *dev_cmd = txq->entries[read_ptr].cmd;
struct iwl_tx_cmd *tx_cmd = (void *)dev_cmd->payload;
WARN_ON(read_ptr >= TFD_QUEUE_SIZE_MAX);
if (txq_id != trans->txqs.cmd.q_id)
sta_id = tx_cmd->sta_id;
bc_ent = cpu_to_le16(1 | (sta_id << 12));
scd_bc_tbl[txq_id].tfd_offset[read_ptr] = bc_ent;
if (read_ptr < TFD_QUEUE_SIZE_BC_DUP)
scd_bc_tbl[txq_id].tfd_offset[TFD_QUEUE_SIZE_MAX + read_ptr] =
bc_ent;
}
void iwl_txq_free_tfd(struct iwl_trans *trans, struct iwl_txq *txq)
{
int rd_ptr = txq->read_ptr;
int idx = iwl_txq_get_cmd_index(txq, rd_ptr);
struct sk_buff *skb;
lockdep_assert_held(&txq->lock);
if (!txq->entries)
return;
if (trans->trans_cfg->gen2)
iwl_txq_gen2_tfd_unmap(trans, &txq->entries[idx].meta,
iwl_txq_get_tfd(trans, txq, rd_ptr));
else
iwl_txq_gen1_tfd_unmap(trans, &txq->entries[idx].meta,
txq, rd_ptr);
skb = txq->entries[idx].skb;
if (skb) {
iwl_op_mode_free_skb(trans->op_mode, skb);
txq->entries[idx].skb = NULL;
}
}
void iwl_txq_progress(struct iwl_txq *txq)
{
lockdep_assert_held(&txq->lock);
if (!txq->wd_timeout)
return;
if (txq->frozen)
return;
if (txq->read_ptr == txq->write_ptr)
del_timer(&txq->stuck_timer);
else
mod_timer(&txq->stuck_timer, jiffies + txq->wd_timeout);
}
void iwl_txq_reclaim(struct iwl_trans *trans, int txq_id, int ssn,
struct sk_buff_head *skbs)
{
struct iwl_txq *txq = trans->txqs.txq[txq_id];
int tfd_num, read_ptr, last_to_free;
if (WARN_ON(txq_id == trans->txqs.cmd.q_id))
return;
if (WARN_ON(!txq))
return;
tfd_num = iwl_txq_get_cmd_index(txq, ssn);
read_ptr = iwl_txq_get_cmd_index(txq, txq->read_ptr);
spin_lock_bh(&txq->lock);
if (!test_bit(txq_id, trans->txqs.queue_used)) {
IWL_DEBUG_TX_QUEUES(trans, "Q %d inactive - ignoring idx %d\n",
txq_id, ssn);
goto out;
}
if (read_ptr == tfd_num)
goto out;
IWL_DEBUG_TX_REPLY(trans, "[Q %d] %d -> %d (%d)\n",
txq_id, txq->read_ptr, tfd_num, ssn);
last_to_free = iwl_txq_dec_wrap(trans, tfd_num);
if (!iwl_txq_used(txq, last_to_free)) {
IWL_ERR(trans,
"%s: Read index for txq id (%d), last_to_free %d is out of range [0-%d] %d %d.\n",
__func__, txq_id, last_to_free,
trans->trans_cfg->base_params->max_tfd_queue_size,
txq->write_ptr, txq->read_ptr);
iwl_op_mode_time_point(trans->op_mode,
IWL_FW_INI_TIME_POINT_FAKE_TX,
NULL);
goto out;
}
if (WARN_ON(!skb_queue_empty(skbs)))
goto out;
for (;
read_ptr != tfd_num;
txq->read_ptr = iwl_txq_inc_wrap(trans, txq->read_ptr),
read_ptr = iwl_txq_get_cmd_index(txq, txq->read_ptr)) {
struct sk_buff *skb = txq->entries[read_ptr].skb;
if (WARN_ON_ONCE(!skb))
continue;
iwl_txq_free_tso_page(trans, skb);
__skb_queue_tail(skbs, skb);
txq->entries[read_ptr].skb = NULL;
if (!trans->trans_cfg->gen2)
iwl_txq_gen1_inval_byte_cnt_tbl(trans, txq);
iwl_txq_free_tfd(trans, txq);
}
iwl_txq_progress(txq);
if (iwl_txq_space(trans, txq) > txq->low_mark &&
test_bit(txq_id, trans->txqs.queue_stopped)) {
struct sk_buff_head overflow_skbs;
__skb_queue_head_init(&overflow_skbs);
skb_queue_splice_init(&txq->overflow_q, &overflow_skbs);
txq->overflow_tx = true;
spin_unlock_bh(&txq->lock);
while (!skb_queue_empty(&overflow_skbs)) {
struct sk_buff *skb = __skb_dequeue(&overflow_skbs);
struct iwl_device_tx_cmd *dev_cmd_ptr;
dev_cmd_ptr = *(void **)((u8 *)skb->cb +
trans->txqs.dev_cmd_offs);
iwl_trans_tx(trans, skb, dev_cmd_ptr, txq_id);
}
if (iwl_txq_space(trans, txq) > txq->low_mark)
iwl_wake_queue(trans, txq);
spin_lock_bh(&txq->lock);
txq->overflow_tx = false;
}
out:
spin_unlock_bh(&txq->lock);
}
void iwl_txq_set_q_ptrs(struct iwl_trans *trans, int txq_id, int ptr)
{
struct iwl_txq *txq = trans->txqs.txq[txq_id];
spin_lock_bh(&txq->lock);
txq->write_ptr = ptr;
txq->read_ptr = txq->write_ptr;
spin_unlock_bh(&txq->lock);
}
void iwl_trans_txq_freeze_timer(struct iwl_trans *trans, unsigned long txqs,
bool freeze)
{
int queue;
for_each_set_bit(queue, &txqs, BITS_PER_LONG) {
struct iwl_txq *txq = trans->txqs.txq[queue];
unsigned long now;
spin_lock_bh(&txq->lock);
now = jiffies;
if (txq->frozen == freeze)
goto next_queue;
IWL_DEBUG_TX_QUEUES(trans, "%s TXQ %d\n",
freeze ? "Freezing" : "Waking", queue);
txq->frozen = freeze;
if (txq->read_ptr == txq->write_ptr)
goto next_queue;
if (freeze) {
if (unlikely(time_after(now,
txq->stuck_timer.expires))) {
goto next_queue;
}
txq->frozen_expiry_remainder =
txq->stuck_timer.expires - now;
del_timer(&txq->stuck_timer);
goto next_queue;
}
mod_timer(&txq->stuck_timer,
now + txq->frozen_expiry_remainder);
next_queue:
spin_unlock_bh(&txq->lock);
}
}
#define HOST_COMPLETE_TIMEOUT (2 * HZ)
static int iwl_trans_txq_send_hcmd_sync(struct iwl_trans *trans,
struct iwl_host_cmd *cmd)
{
const char *cmd_str = iwl_get_cmd_string(trans, cmd->id);
struct iwl_txq *txq = trans->txqs.txq[trans->txqs.cmd.q_id];
int cmd_idx;
int ret;
IWL_DEBUG_INFO(trans, "Attempting to send sync command %s\n", cmd_str);
if (WARN(test_and_set_bit(STATUS_SYNC_HCMD_ACTIVE,
&trans->status),
"Command %s: a command is already active!\n", cmd_str))
return -EIO;
IWL_DEBUG_INFO(trans, "Setting HCMD_ACTIVE for command %s\n", cmd_str);
cmd_idx = trans->ops->send_cmd(trans, cmd);
if (cmd_idx < 0) {
ret = cmd_idx;
clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
IWL_ERR(trans, "Error sending %s: enqueue_hcmd failed: %d\n",
cmd_str, ret);
return ret;
}
ret = wait_event_timeout(trans->wait_command_queue,
!test_bit(STATUS_SYNC_HCMD_ACTIVE,
&trans->status),
HOST_COMPLETE_TIMEOUT);
if (!ret) {
IWL_ERR(trans, "Error sending %s: time out after %dms.\n",
cmd_str, jiffies_to_msecs(HOST_COMPLETE_TIMEOUT));
IWL_ERR(trans, "Current CMD queue read_ptr %d write_ptr %d\n",
txq->read_ptr, txq->write_ptr);
clear_bit(STATUS_SYNC_HCMD_ACTIVE, &trans->status);
IWL_DEBUG_INFO(trans, "Clearing HCMD_ACTIVE for command %s\n",
cmd_str);
ret = -ETIMEDOUT;
iwl_trans_sync_nmi(trans);
goto cancel;
}
if (test_bit(STATUS_FW_ERROR, &trans->status)) {
if (!test_and_clear_bit(STATUS_SUPPRESS_CMD_ERROR_ONCE,
&trans->status)) {
IWL_ERR(trans, "FW error in SYNC CMD %s\n", cmd_str);
dump_stack();
}
ret = -EIO;
goto cancel;
}
if (!(cmd->flags & CMD_SEND_IN_RFKILL) &&
test_bit(STATUS_RFKILL_OPMODE, &trans->status)) {
IWL_DEBUG_RF_KILL(trans, "RFKILL in SYNC CMD... no rsp\n");
ret = -ERFKILL;
goto cancel;
}
if ((cmd->flags & CMD_WANT_SKB) && !cmd->resp_pkt) {
IWL_ERR(trans, "Error: Response NULL in '%s'\n", cmd_str);
ret = -EIO;
goto cancel;
}
return 0;
cancel:
if (cmd->flags & CMD_WANT_SKB) {
txq->entries[cmd_idx].meta.flags &= ~CMD_WANT_SKB;
}
if (cmd->resp_pkt) {
iwl_free_resp(cmd);
cmd->resp_pkt = NULL;
}
return ret;
}
int iwl_trans_txq_send_hcmd(struct iwl_trans *trans,
struct iwl_host_cmd *cmd)
{
if (test_bit(STATUS_TRANS_DEAD, &trans->status))
return -ENODEV;
if (!(cmd->flags & CMD_SEND_IN_RFKILL) &&
test_bit(STATUS_RFKILL_OPMODE, &trans->status)) {
IWL_DEBUG_RF_KILL(trans, "Dropping CMD 0x%x: RF KILL\n",
cmd->id);
return -ERFKILL;
}
if (unlikely(trans->system_pm_mode == IWL_PLAT_PM_MODE_D3 &&
!(cmd->flags & CMD_SEND_IN_D3))) {
IWL_DEBUG_WOWLAN(trans, "Dropping CMD 0x%x: D3\n", cmd->id);
return -EHOSTDOWN;
}
if (cmd->flags & CMD_ASYNC) {
int ret;
if (WARN_ON(cmd->flags & CMD_WANT_SKB))
return -EINVAL;
ret = trans->ops->send_cmd(trans, cmd);
if (ret < 0) {
IWL_ERR(trans,
"Error sending %s: enqueue_hcmd failed: %d\n",
iwl_get_cmd_string(trans, cmd->id), ret);
return ret;
}
return 0;
}
return iwl_trans_txq_send_hcmd_sync(trans, cmd);
}