/* * Copyright (c) 2013 Eugene Krasnikov <k.eugene.e@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* DXE - DMA transfer engine * we have 2 channels(High prio and Low prio) for TX and 2 channels for RX. * through low channels data packets are transfered * through high channels managment packets are transfered
*/
/* DXE control block allocation */
ret = wcn36xx_dxe_allocate_ctl_block(&wcn->dxe_tx_l_ch); if (ret) goto out_err;
ret = wcn36xx_dxe_allocate_ctl_block(&wcn->dxe_tx_h_ch); if (ret) goto out_err;
ret = wcn36xx_dxe_allocate_ctl_block(&wcn->dxe_rx_l_ch); if (ret) goto out_err;
ret = wcn36xx_dxe_allocate_ctl_block(&wcn->dxe_rx_h_ch); if (ret) goto out_err;
/* Initialize SMSM state Clear TX Enable RING EMPTY STATE */
ret = qcom_smem_state_update_bits(wcn->tx_enable_state,
WCN36XX_SMSM_WLAN_TX_ENABLE |
WCN36XX_SMSM_WLAN_TX_RINGS_EMPTY,
WCN36XX_SMSM_WLAN_TX_RINGS_EMPTY); if (ret) goto out_err;
return 0;
out_err:
wcn36xx_err("Failed to allocate DXE control blocks\n");
wcn36xx_dxe_free_ctl_blks(wcn); return -ENOMEM;
}
for (i = 0; i < ch->desc_num; i++) { /* Only every second dxe needs a bd pointer,
the other will point to the skb data */ if (!(i & 1)) {
cur->bd_phy_addr = bd_phy_addr;
cur->bd_cpu_addr = bd_cpu_addr;
bd_phy_addr += chunk_size;
bd_cpu_addr += chunk_size;
} else {
cur->bd_phy_addr = 0;
cur->bd_cpu_addr = NULL;
}
cur = cur->next;
}
}
/* * Make at least one loop of do-while because in case ring is * completely full head and tail are pointing to the same element * and while-do will not make any cycles.
*/
spin_lock_irqsave(&ch->lock, flags);
ctl = ch->tail_blk_ctl; do { if (READ_ONCE(ctl->desc->ctrl) & WCN36xx_DXE_CTRL_VLD) break;
if (ctl->skb &&
READ_ONCE(ctl->desc->ctrl) & WCN36xx_DXE_CTRL_EOP) {
dma_unmap_single(wcn->dev, ctl->desc->src_addr_l,
ctl->skb->len, DMA_TO_DEVICE);
info = IEEE80211_SKB_CB(ctl->skb); if (info->flags & IEEE80211_TX_CTL_REQ_TX_STATUS) { if (info->flags & IEEE80211_TX_CTL_NO_ACK) {
info->flags |= IEEE80211_TX_STAT_NOACK_TRANSMITTED;
ieee80211_tx_status_irqsafe(wcn->hw, ctl->skb);
} else { /* Wait for the TX ack indication or timeout... */
spin_lock(&wcn->dxe_lock); if (WARN_ON(wcn->tx_ack_skb))
ieee80211_free_txskb(wcn->hw, wcn->tx_ack_skb);
wcn->tx_ack_skb = ctl->skb; /* Tracking ref */
mod_timer(&wcn->tx_ack_timer, jiffies + HZ / 10);
spin_unlock(&wcn->dxe_lock);
} /* do not free, ownership transferred to mac80211 status cb */
} else {
ieee80211_free_txskb(wcn->hw, ctl->skb);
}
if (wcn->queues_stopped) {
wcn->queues_stopped = false;
ieee80211_wake_queues(wcn->hw);
}
if (int_reason & WCN36XX_CH_STAT_INT_ERR_MASK) {
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_0_INT_ERR_CLR,
int_mask);
wcn36xx_err("DXE IRQ reported error on RX channel\n");
}
if (int_reason & WCN36XX_CH_STAT_INT_DONE_MASK)
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_0_INT_DONE_CLR,
int_mask);
if (int_reason & WCN36XX_CH_STAT_INT_ED_MASK)
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_0_INT_ED_CLR,
int_mask);
if (!(int_reason & (WCN36XX_CH_STAT_INT_DONE_MASK |
WCN36XX_CH_STAT_INT_ED_MASK))) return 0;
spin_lock(&ch->lock);
ctl = ch->head_blk_ctl;
dxe = ctl->desc;
while (!(READ_ONCE(dxe->ctrl) & WCN36xx_DXE_CTRL_VLD)) { /* do not read until we own DMA descriptor */
dma_rmb();
/* read/modify DMA descriptor */
skb = ctl->skb;
dma_addr = dxe->dst_addr_l;
ret = wcn36xx_dxe_fill_skb(wcn->dev, ctl, GFP_ATOMIC); if (0 == ret) { /* new skb allocation ok. Use the new one and queue * the old one to network system.
*/
dma_unmap_single(wcn->dev, dma_addr, WCN36XX_PKT_SIZE,
DMA_FROM_DEVICE);
wcn36xx_rx_skb(wcn, skb);
} /* else keep old skb not submitted and reuse it for rx DMA * (dropping the packet that it contained)
*/
/* flush descriptor changes before re-marking as valid */
dma_wmb();
dxe->ctrl = ctrl;
/* * If skb is not null that means that we reached the tail of the ring * hence ring is full. Stop queues to let mac80211 back off until ring * has an empty slot again.
*/ if (NULL != ctl_skb->skb) {
ieee80211_stop_queues(wcn->hw);
wcn->queues_stopped = true;
spin_unlock_irqrestore(&ch->lock, flags); return -EBUSY;
}
if (unlikely(ctl_skb->bd_cpu_addr)) {
wcn36xx_err("bd_cpu_addr cannot be NULL for skb DXE\n");
ret = -EINVAL; goto unlock;
}
/* Set source address of the BD we send */
desc_bd->src_addr_l = ctl_bd->bd_phy_addr;
desc_bd->dst_addr_l = ch->dxe_wq;
desc_bd->fr_len = sizeof(struct wcn36xx_tx_bd);
/* Move the head of the ring to the next empty descriptor */
ch->head_blk_ctl = ctl_skb->next;
/* Commit all previous writes and set descriptors to VALID */
wmb();
desc_skb->ctrl = ch->ctrl_skb;
wmb();
desc_bd->ctrl = ch->ctrl_bd;
/* * When connected and trying to send data frame chip can be in sleep * mode and writing to the register will not wake up the chip. Instead * notify chip about new frame through SMSM bus.
*/ if (is_low && vif_priv->pw_state == WCN36XX_BMPS) {
qcom_smem_state_update_bits(wcn->tx_rings_empty_state,
WCN36XX_SMSM_WLAN_TX_ENABLE,
WCN36XX_SMSM_WLAN_TX_ENABLE);
} else { /* indicate End Of Packet and generate interrupt on descriptor * done.
*/
wcn36xx_dxe_write_register(wcn,
ch->reg_ctrl, ch->def_ctrl);
}
ret = 0;
unlock:
spin_unlock_irqrestore(&ch->lock, flags); return ret;
}
int wcn36xx_dxe_tx_flush(struct wcn36xx *wcn)
{ int i = 0;
/* Called with mac80211 queues stopped. Wait for empty HW queues. */ do { if (_wcn36xx_dxe_tx_channel_is_empty(&wcn->dxe_tx_l_ch) &&
_wcn36xx_dxe_tx_channel_is_empty(&wcn->dxe_tx_h_ch)) { return 0;
} /* This ieee80211_ops callback is specifically allowed to * sleep.
*/
usleep_range(1000, 1100);
} while (++i < 100);
return -EBUSY;
}
int wcn36xx_dxe_init(struct wcn36xx *wcn)
{ int reg_data = 0, ret;
/***************************************/ /* Init descriptors for TX LOW channel */ /***************************************/
ret = wcn36xx_dxe_init_descs(wcn, &wcn->dxe_tx_l_ch); if (ret) {
dev_err(wcn->dev, "Error allocating descriptor\n"); return ret;
}
wcn36xx_dxe_init_tx_bd(&wcn->dxe_tx_l_ch, &wcn->data_mem_pool);
/* Write channel head to a NEXT register */
wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_CH_NEXT_DESC_ADDR_TX_L,
wcn->dxe_tx_l_ch.head_blk_ctl->desc_phy_addr);
/* Program DMA destination addr for TX LOW */
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_CH_DEST_ADDR_TX_L,
WCN36XX_DXE_WQ_TX_L(wcn));
/* Write channel head to a NEXT register */
wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_CH_NEXT_DESC_ADDR_TX_H,
wcn->dxe_tx_h_ch.head_blk_ctl->desc_phy_addr);
/* Program DMA destination addr for TX HIGH */
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_CH_DEST_ADDR_TX_H,
WCN36XX_DXE_WQ_TX_H(wcn));
/***************************************/ /* Init descriptors for RX LOW channel */ /***************************************/
ret = wcn36xx_dxe_init_descs(wcn, &wcn->dxe_rx_l_ch); if (ret) {
dev_err(wcn->dev, "Error allocating descriptor\n"); goto out_err_rxl_ch;
}
/* For RX we need to preallocated buffers */
wcn36xx_dxe_ch_alloc_skb(wcn, &wcn->dxe_rx_l_ch);
/* Write channel head to a NEXT register */
wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_CH_NEXT_DESC_ADDR_RX_L,
wcn->dxe_rx_l_ch.head_blk_ctl->desc_phy_addr);
/* Program preallocated destination address */
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_CH_DEST_ADDR_RX_L,
wcn->dxe_rx_l_ch.head_blk_ctl->desc->phy_next_l);
/* Enable default control registers */
wcn36xx_dxe_write_register(wcn,
WCN36XX_DXE_REG_CTL_RX_L,
WCN36XX_DXE_CH_DEFAULT_CTL_RX_L);
/***************************************/ /* Init descriptors for RX HIGH channel */ /***************************************/
ret = wcn36xx_dxe_init_descs(wcn, &wcn->dxe_rx_h_ch); if (ret) {
dev_err(wcn->dev, "Error allocating descriptor\n"); goto out_err_rxh_ch;
}
/* For RX we need to prealocat buffers */
wcn36xx_dxe_ch_alloc_skb(wcn, &wcn->dxe_rx_h_ch);
/* Write chanel head to a NEXT register */
wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_CH_NEXT_DESC_ADDR_RX_H,
wcn->dxe_rx_h_ch.head_blk_ctl->desc_phy_addr);
if (wcn->tx_ack_skb) {
ieee80211_tx_status_irqsafe(wcn->hw, wcn->tx_ack_skb);
wcn->tx_ack_skb = NULL;
}
/* Put the DXE block into reset before freeing memory */
reg_data = WCN36XX_DXE_REG_RESET;
wcn36xx_dxe_write_register(wcn, WCN36XX_DXE_REG_CSR_RESET, reg_data);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.