linux/drivers/net/wireless/microchip/wilc1000/wlan.c

1811 lines
41 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2012 - 2018 Microchip Technology Inc., and its subsidiaries.
* All rights reserved.
*/
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <net/dsfield.h>
#include "cfg80211.h"
#include "wlan_cfg.h"
#define WAKE_UP_TRIAL_RETRY 10000
static void wilc_wlan_txq_remove(struct wilc *wilc, u8 q_num,
struct txq_entry_t *tqe)
{
list_del(&tqe->list);
wilc->txq_entries -= 1;
wilc->txq[q_num].count--;
}
static struct txq_entry_t *
wilc_wlan_txq_remove_from_head(struct wilc *wilc, u8 q_num)
{
struct txq_entry_t *tqe = NULL;
unsigned long flags;
spin_lock_irqsave(&wilc->txq_spinlock, flags);
if (!list_empty(&wilc->txq[q_num].txq_head.list)) {
tqe = list_first_entry(&wilc->txq[q_num].txq_head.list,
struct txq_entry_t, list);
list_del(&tqe->list);
wilc->txq_entries -= 1;
wilc->txq[q_num].count--;
}
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
return tqe;
}
static void wilc_wlan_txq_add_to_tail(struct net_device *dev, u8 q_num,
struct txq_entry_t *tqe)
{
unsigned long flags;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc = vif->wilc;
spin_lock_irqsave(&wilc->txq_spinlock, flags);
list_add_tail(&tqe->list, &wilc->txq[q_num].txq_head.list);
wilc->txq_entries += 1;
wilc->txq[q_num].count++;
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
complete(&wilc->txq_event);
}
static void wilc_wlan_txq_add_to_head(struct wilc_vif *vif, u8 q_num,
struct txq_entry_t *tqe)
{
unsigned long flags;
struct wilc *wilc = vif->wilc;
mutex_lock(&wilc->txq_add_to_head_cs);
spin_lock_irqsave(&wilc->txq_spinlock, flags);
list_add(&tqe->list, &wilc->txq[q_num].txq_head.list);
wilc->txq_entries += 1;
wilc->txq[q_num].count++;
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
mutex_unlock(&wilc->txq_add_to_head_cs);
complete(&wilc->txq_event);
}
#define NOT_TCP_ACK (-1)
static inline void add_tcp_session(struct wilc_vif *vif, u32 src_prt,
u32 dst_prt, u32 seq)
{
struct tcp_ack_filter *f = &vif->ack_filter;
if (f->tcp_session < 2 * MAX_TCP_SESSION) {
f->ack_session_info[f->tcp_session].seq_num = seq;
f->ack_session_info[f->tcp_session].bigger_ack_num = 0;
f->ack_session_info[f->tcp_session].src_port = src_prt;
f->ack_session_info[f->tcp_session].dst_port = dst_prt;
f->tcp_session++;
}
}
static inline void update_tcp_session(struct wilc_vif *vif, u32 index, u32 ack)
{
struct tcp_ack_filter *f = &vif->ack_filter;
if (index < 2 * MAX_TCP_SESSION &&
ack > f->ack_session_info[index].bigger_ack_num)
f->ack_session_info[index].bigger_ack_num = ack;
}
static inline void add_tcp_pending_ack(struct wilc_vif *vif, u32 ack,
u32 session_index,
struct txq_entry_t *txqe)
{
struct tcp_ack_filter *f = &vif->ack_filter;
u32 i = f->pending_base + f->pending_acks_idx;
if (i < MAX_PENDING_ACKS) {
f->pending_acks[i].ack_num = ack;
f->pending_acks[i].txqe = txqe;
f->pending_acks[i].session_index = session_index;
txqe->ack_idx = i;
f->pending_acks_idx++;
}
}
static inline void tcp_process(struct net_device *dev, struct txq_entry_t *tqe)
{
void *buffer = tqe->buffer;
const struct ethhdr *eth_hdr_ptr = buffer;
int i;
unsigned long flags;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc = vif->wilc;
struct tcp_ack_filter *f = &vif->ack_filter;
const struct iphdr *ip_hdr_ptr;
const struct tcphdr *tcp_hdr_ptr;
u32 ihl, total_length, data_offset;
spin_lock_irqsave(&wilc->txq_spinlock, flags);
if (eth_hdr_ptr->h_proto != htons(ETH_P_IP))
goto out;
ip_hdr_ptr = buffer + ETH_HLEN;
if (ip_hdr_ptr->protocol != IPPROTO_TCP)
goto out;
ihl = ip_hdr_ptr->ihl << 2;
tcp_hdr_ptr = buffer + ETH_HLEN + ihl;
total_length = ntohs(ip_hdr_ptr->tot_len);
data_offset = tcp_hdr_ptr->doff << 2;
if (total_length == (ihl + data_offset)) {
u32 seq_no, ack_no;
seq_no = ntohl(tcp_hdr_ptr->seq);
ack_no = ntohl(tcp_hdr_ptr->ack_seq);
for (i = 0; i < f->tcp_session; i++) {
u32 j = f->ack_session_info[i].seq_num;
if (i < 2 * MAX_TCP_SESSION &&
j == seq_no) {
update_tcp_session(vif, i, ack_no);
break;
}
}
if (i == f->tcp_session)
add_tcp_session(vif, 0, 0, seq_no);
add_tcp_pending_ack(vif, ack_no, i, tqe);
}
out:
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
}
static void wilc_wlan_txq_filter_dup_tcp_ack(struct net_device *dev)
{
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc = vif->wilc;
struct tcp_ack_filter *f = &vif->ack_filter;
u32 i = 0;
u32 dropped = 0;
unsigned long flags;
spin_lock_irqsave(&wilc->txq_spinlock, flags);
for (i = f->pending_base;
i < (f->pending_base + f->pending_acks_idx); i++) {
u32 index;
u32 bigger_ack_num;
if (i >= MAX_PENDING_ACKS)
break;
index = f->pending_acks[i].session_index;
if (index >= 2 * MAX_TCP_SESSION)
break;
bigger_ack_num = f->ack_session_info[index].bigger_ack_num;
if (f->pending_acks[i].ack_num < bigger_ack_num) {
struct txq_entry_t *tqe;
tqe = f->pending_acks[i].txqe;
if (tqe) {
wilc_wlan_txq_remove(wilc, tqe->q_num, tqe);
tqe->status = 1;
if (tqe->tx_complete_func)
tqe->tx_complete_func(tqe->priv,
tqe->status);
kfree(tqe);
dropped++;
}
}
}
f->pending_acks_idx = 0;
f->tcp_session = 0;
if (f->pending_base == 0)
f->pending_base = MAX_TCP_SESSION;
else
f->pending_base = 0;
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
while (dropped > 0) {
wait_for_completion_timeout(&wilc->txq_event,
msecs_to_jiffies(1));
dropped--;
}
}
void wilc_enable_tcp_ack_filter(struct wilc_vif *vif, bool value)
{
vif->ack_filter.enabled = value;
}
static int wilc_wlan_txq_add_cfg_pkt(struct wilc_vif *vif, u8 *buffer,
u32 buffer_size)
{
struct txq_entry_t *tqe;
struct wilc *wilc = vif->wilc;
netdev_dbg(vif->ndev, "Adding config packet ...\n");
if (wilc->quit) {
netdev_dbg(vif->ndev, "Return due to clear function\n");
complete(&wilc->cfg_event);
return 0;
}
tqe = kmalloc(sizeof(*tqe), GFP_ATOMIC);
if (!tqe) {
complete(&wilc->cfg_event);
return 0;
}
tqe->type = WILC_CFG_PKT;
tqe->buffer = buffer;
tqe->buffer_size = buffer_size;
tqe->tx_complete_func = NULL;
tqe->priv = NULL;
tqe->q_num = AC_VO_Q;
tqe->ack_idx = NOT_TCP_ACK;
tqe->vif = vif;
wilc_wlan_txq_add_to_head(vif, AC_VO_Q, tqe);
return 1;
}
static bool is_ac_q_limit(struct wilc *wl, u8 q_num)
{
u8 factors[NQUEUES] = {1, 1, 1, 1};
u16 i;
unsigned long flags;
struct wilc_tx_queue_status *q = &wl->tx_q_limit;
u8 end_index;
u8 q_limit;
bool ret = false;
spin_lock_irqsave(&wl->txq_spinlock, flags);
if (!q->initialized) {
for (i = 0; i < AC_BUFFER_SIZE; i++)
q->buffer[i] = i % NQUEUES;
for (i = 0; i < NQUEUES; i++) {
q->cnt[i] = AC_BUFFER_SIZE * factors[i] / NQUEUES;
q->sum += q->cnt[i];
}
q->end_index = AC_BUFFER_SIZE - 1;
q->initialized = 1;
}
end_index = q->end_index;
q->cnt[q->buffer[end_index]] -= factors[q->buffer[end_index]];
q->cnt[q_num] += factors[q_num];
q->sum += (factors[q_num] - factors[q->buffer[end_index]]);
q->buffer[end_index] = q_num;
if (end_index > 0)
q->end_index--;
else
q->end_index = AC_BUFFER_SIZE - 1;
if (!q->sum)
q_limit = 1;
else
q_limit = (q->cnt[q_num] * FLOW_CONTROL_UPPER_THRESHOLD / q->sum) + 1;
if (wl->txq[q_num].count <= q_limit)
ret = true;
spin_unlock_irqrestore(&wl->txq_spinlock, flags);
return ret;
}
static inline u8 ac_classify(struct wilc *wilc, struct sk_buff *skb)
{
u8 q_num = AC_BE_Q;
u8 dscp;
switch (skb->protocol) {
case htons(ETH_P_IP):
dscp = ipv4_get_dsfield(ip_hdr(skb)) & 0xfc;
break;
case htons(ETH_P_IPV6):
dscp = ipv6_get_dsfield(ipv6_hdr(skb)) & 0xfc;
break;
default:
return q_num;
}
switch (dscp) {
case 0x08:
case 0x20:
case 0x40:
q_num = AC_BK_Q;
break;
case 0x80:
case 0xA0:
case 0x28:
q_num = AC_VI_Q;
break;
case 0xC0:
case 0xD0:
case 0xE0:
case 0x88:
case 0xB8:
q_num = AC_VO_Q;
break;
}
return q_num;
}
static inline int ac_balance(struct wilc *wl, u8 *ratio)
{
u8 i, max_count = 0;
if (!ratio)
return -EINVAL;
for (i = 0; i < NQUEUES; i++)
if (wl->txq[i].fw.count > max_count)
max_count = wl->txq[i].fw.count;
for (i = 0; i < NQUEUES; i++)
ratio[i] = max_count - wl->txq[i].fw.count;
return 0;
}
static inline void ac_update_fw_ac_pkt_info(struct wilc *wl, u32 reg)
{
wl->txq[AC_BK_Q].fw.count = FIELD_GET(BK_AC_COUNT_FIELD, reg);
wl->txq[AC_BE_Q].fw.count = FIELD_GET(BE_AC_COUNT_FIELD, reg);
wl->txq[AC_VI_Q].fw.count = FIELD_GET(VI_AC_COUNT_FIELD, reg);
wl->txq[AC_VO_Q].fw.count = FIELD_GET(VO_AC_COUNT_FIELD, reg);
wl->txq[AC_BK_Q].fw.acm = FIELD_GET(BK_AC_ACM_STAT_FIELD, reg);
wl->txq[AC_BE_Q].fw.acm = FIELD_GET(BE_AC_ACM_STAT_FIELD, reg);
wl->txq[AC_VI_Q].fw.acm = FIELD_GET(VI_AC_ACM_STAT_FIELD, reg);
wl->txq[AC_VO_Q].fw.acm = FIELD_GET(VO_AC_ACM_STAT_FIELD, reg);
}
static inline u8 ac_change(struct wilc *wilc, u8 *ac)
{
do {
if (wilc->txq[*ac].fw.acm == 0)
return 0;
(*ac)++;
} while (*ac < NQUEUES);
return 1;
}
int wilc_wlan_txq_add_net_pkt(struct net_device *dev,
struct tx_complete_data *tx_data, u8 *buffer,
u32 buffer_size,
void (*tx_complete_fn)(void *, int))
{
struct txq_entry_t *tqe;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc;
u8 q_num;
wilc = vif->wilc;
if (wilc->quit) {
tx_complete_fn(tx_data, 0);
return 0;
}
if (!wilc->initialized) {
tx_complete_fn(tx_data, 0);
return 0;
}
tqe = kmalloc(sizeof(*tqe), GFP_ATOMIC);
if (!tqe) {
tx_complete_fn(tx_data, 0);
return 0;
}
tqe->type = WILC_NET_PKT;
tqe->buffer = buffer;
tqe->buffer_size = buffer_size;
tqe->tx_complete_func = tx_complete_fn;
tqe->priv = tx_data;
tqe->vif = vif;
q_num = ac_classify(wilc, tx_data->skb);
tqe->q_num = q_num;
if (ac_change(wilc, &q_num)) {
tx_complete_fn(tx_data, 0);
kfree(tqe);
return 0;
}
if (is_ac_q_limit(wilc, q_num)) {
tqe->ack_idx = NOT_TCP_ACK;
if (vif->ack_filter.enabled)
tcp_process(dev, tqe);
wilc_wlan_txq_add_to_tail(dev, q_num, tqe);
} else {
tx_complete_fn(tx_data, 0);
kfree(tqe);
}
return wilc->txq_entries;
}
int wilc_wlan_txq_add_mgmt_pkt(struct net_device *dev, void *priv, u8 *buffer,
u32 buffer_size,
void (*tx_complete_fn)(void *, int))
{
struct txq_entry_t *tqe;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc;
wilc = vif->wilc;
if (wilc->quit) {
tx_complete_fn(priv, 0);
return 0;
}
if (!wilc->initialized) {
tx_complete_fn(priv, 0);
return 0;
}
tqe = kmalloc(sizeof(*tqe), GFP_ATOMIC);
if (!tqe) {
tx_complete_fn(priv, 0);
return 0;
}
tqe->type = WILC_MGMT_PKT;
tqe->buffer = buffer;
tqe->buffer_size = buffer_size;
tqe->tx_complete_func = tx_complete_fn;
tqe->priv = priv;
tqe->q_num = AC_BE_Q;
tqe->ack_idx = NOT_TCP_ACK;
tqe->vif = vif;
wilc_wlan_txq_add_to_tail(dev, AC_VO_Q, tqe);
return 1;
}
static struct txq_entry_t *wilc_wlan_txq_get_first(struct wilc *wilc, u8 q_num)
{
struct txq_entry_t *tqe = NULL;
unsigned long flags;
spin_lock_irqsave(&wilc->txq_spinlock, flags);
if (!list_empty(&wilc->txq[q_num].txq_head.list))
tqe = list_first_entry(&wilc->txq[q_num].txq_head.list,
struct txq_entry_t, list);
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
return tqe;
}
static struct txq_entry_t *wilc_wlan_txq_get_next(struct wilc *wilc,
struct txq_entry_t *tqe,
u8 q_num)
{
unsigned long flags;
spin_lock_irqsave(&wilc->txq_spinlock, flags);
if (!list_is_last(&tqe->list, &wilc->txq[q_num].txq_head.list))
tqe = list_next_entry(tqe, list);
else
tqe = NULL;
spin_unlock_irqrestore(&wilc->txq_spinlock, flags);
return tqe;
}
static void wilc_wlan_rxq_add(struct wilc *wilc, struct rxq_entry_t *rqe)
{
if (wilc->quit)
return;
mutex_lock(&wilc->rxq_cs);
list_add_tail(&rqe->list, &wilc->rxq_head.list);
mutex_unlock(&wilc->rxq_cs);
}
static struct rxq_entry_t *wilc_wlan_rxq_remove(struct wilc *wilc)
{
struct rxq_entry_t *rqe = NULL;
mutex_lock(&wilc->rxq_cs);
if (!list_empty(&wilc->rxq_head.list)) {
rqe = list_first_entry(&wilc->rxq_head.list, struct rxq_entry_t,
list);
list_del(&rqe->list);
}
mutex_unlock(&wilc->rxq_cs);
return rqe;
}
static int chip_allow_sleep_wilc1000(struct wilc *wilc)
{
u32 reg = 0;
const struct wilc_hif_func *hif_func = wilc->hif_func;
u32 wakeup_reg, wakeup_bit;
u32 to_host_from_fw_reg, to_host_from_fw_bit;
u32 from_host_to_fw_reg, from_host_to_fw_bit;
u32 trials = 100;
int ret;
if (wilc->io_type == WILC_HIF_SDIO) {
wakeup_reg = WILC_SDIO_WAKEUP_REG;
wakeup_bit = WILC_SDIO_WAKEUP_BIT;
from_host_to_fw_reg = WILC_SDIO_HOST_TO_FW_REG;
from_host_to_fw_bit = WILC_SDIO_HOST_TO_FW_BIT;
to_host_from_fw_reg = WILC_SDIO_FW_TO_HOST_REG;
to_host_from_fw_bit = WILC_SDIO_FW_TO_HOST_BIT;
} else {
wakeup_reg = WILC_SPI_WAKEUP_REG;
wakeup_bit = WILC_SPI_WAKEUP_BIT;
from_host_to_fw_reg = WILC_SPI_HOST_TO_FW_REG;
from_host_to_fw_bit = WILC_SPI_HOST_TO_FW_BIT;
to_host_from_fw_reg = WILC_SPI_FW_TO_HOST_REG;
to_host_from_fw_bit = WILC_SPI_FW_TO_HOST_BIT;
}
while (--trials) {
ret = hif_func->hif_read_reg(wilc, to_host_from_fw_reg, &reg);
if (ret)
return ret;
if ((reg & to_host_from_fw_bit) == 0)
break;
}
if (!trials)
pr_warn("FW not responding\n");
/* Clear bit 1 */
ret = hif_func->hif_read_reg(wilc, wakeup_reg, &reg);
if (ret)
return ret;
if (reg & wakeup_bit) {
reg &= ~wakeup_bit;
ret = hif_func->hif_write_reg(wilc, wakeup_reg, reg);
if (ret)
return ret;
}
ret = hif_func->hif_read_reg(wilc, from_host_to_fw_reg, &reg);
if (ret)
return ret;
if (reg & from_host_to_fw_bit) {
reg &= ~from_host_to_fw_bit;
ret = hif_func->hif_write_reg(wilc, from_host_to_fw_reg, reg);
if (ret)
return ret;
}
return 0;
}
static int chip_allow_sleep_wilc3000(struct wilc *wilc)
{
u32 reg = 0;
int ret;
const struct wilc_hif_func *hif_func = wilc->hif_func;
if (wilc->io_type == WILC_HIF_SDIO) {
ret = hif_func->hif_read_reg(wilc, WILC_SDIO_WAKEUP_REG, &reg);
if (ret)
return ret;
ret = hif_func->hif_write_reg(wilc, WILC_SDIO_WAKEUP_REG,
reg & ~WILC_SDIO_WAKEUP_BIT);
if (ret)
return ret;
} else {
ret = hif_func->hif_read_reg(wilc, WILC_SPI_WAKEUP_REG, &reg);
if (ret)
return ret;
ret = hif_func->hif_write_reg(wilc, WILC_SPI_WAKEUP_REG,
reg & ~WILC_SPI_WAKEUP_BIT);
if (ret)
return ret;
}
return 0;
}
static int chip_allow_sleep(struct wilc *wilc)
{
if (is_wilc1000(wilc->chipid))
return chip_allow_sleep_wilc1000(wilc);
else
return chip_allow_sleep_wilc3000(wilc);
}
static int chip_wakeup_wilc1000(struct wilc *wilc)
{
u32 ret = 0;
u32 clk_status_val = 0, trials = 0;
u32 wakeup_reg, wakeup_bit;
u32 clk_status_reg, clk_status_bit;
u32 from_host_to_fw_reg, from_host_to_fw_bit;
const struct wilc_hif_func *hif_func = wilc->hif_func;
if (wilc->io_type == WILC_HIF_SDIO) {
wakeup_reg = WILC_SDIO_WAKEUP_REG;
wakeup_bit = WILC_SDIO_WAKEUP_BIT;
clk_status_reg = WILC1000_SDIO_CLK_STATUS_REG;
clk_status_bit = WILC1000_SDIO_CLK_STATUS_BIT;
from_host_to_fw_reg = WILC_SDIO_HOST_TO_FW_REG;
from_host_to_fw_bit = WILC_SDIO_HOST_TO_FW_BIT;
} else {
wakeup_reg = WILC_SPI_WAKEUP_REG;
wakeup_bit = WILC_SPI_WAKEUP_BIT;
clk_status_reg = WILC1000_SPI_CLK_STATUS_REG;
clk_status_bit = WILC1000_SPI_CLK_STATUS_BIT;
from_host_to_fw_reg = WILC_SPI_HOST_TO_FW_REG;
from_host_to_fw_bit = WILC_SPI_HOST_TO_FW_BIT;
}
/* indicate host wakeup */
ret = hif_func->hif_write_reg(wilc, from_host_to_fw_reg,
from_host_to_fw_bit);
if (ret)
return ret;
/* Set wake-up bit */
ret = hif_func->hif_write_reg(wilc, wakeup_reg,
wakeup_bit);
if (ret)
return ret;
while (trials < WAKE_UP_TRIAL_RETRY) {
ret = hif_func->hif_read_reg(wilc, clk_status_reg,
&clk_status_val);
if (ret) {
pr_err("Bus error %d %x\n", ret, clk_status_val);
return ret;
}
if (clk_status_val & clk_status_bit)
break;
trials++;
}
if (trials >= WAKE_UP_TRIAL_RETRY) {
pr_err("Failed to wake-up the chip\n");
return -ETIMEDOUT;
}
/* Sometimes spi fail to read clock regs after reading
* writing clockless registers
*/
if (wilc->io_type == WILC_HIF_SPI)
wilc->hif_func->hif_reset(wilc);
return 0;
}
static int chip_wakeup_wilc3000(struct wilc *wilc)
{
u32 wakeup_reg_val, clk_status_reg_val, trials = 0;
u32 wakeup_reg, wakeup_bit;
u32 clk_status_reg, clk_status_bit;
int wake_seq_trials = 5;
const struct wilc_hif_func *hif_func = wilc->hif_func;
if (wilc->io_type == WILC_HIF_SDIO) {
wakeup_reg = WILC_SDIO_WAKEUP_REG;
wakeup_bit = WILC_SDIO_WAKEUP_BIT;
clk_status_reg = WILC3000_SDIO_CLK_STATUS_REG;
clk_status_bit = WILC3000_SDIO_CLK_STATUS_BIT;
} else {
wakeup_reg = WILC_SPI_WAKEUP_REG;
wakeup_bit = WILC_SPI_WAKEUP_BIT;
clk_status_reg = WILC3000_SPI_CLK_STATUS_REG;
clk_status_bit = WILC3000_SPI_CLK_STATUS_BIT;
}
hif_func->hif_read_reg(wilc, wakeup_reg, &wakeup_reg_val);
do {
hif_func->hif_write_reg(wilc, wakeup_reg, wakeup_reg_val |
wakeup_bit);
/* Check the clock status */
hif_func->hif_read_reg(wilc, clk_status_reg,
&clk_status_reg_val);
/* In case of clocks off, wait 1ms, and check it again.
* if still off, wait for another 1ms, for a total wait of 3ms.
* If still off, redo the wake up sequence
*/
while ((clk_status_reg_val & clk_status_bit) == 0 &&
(++trials % 4) != 0) {
/* Wait for the chip to stabilize*/
usleep_range(1000, 1100);
/* Make sure chip is awake. This is an extra step that
* can be removed later to avoid the bus access
* overhead
*/
hif_func->hif_read_reg(wilc, clk_status_reg,
&clk_status_reg_val);
}
/* in case of failure, Reset the wakeup bit to introduce a new
* edge on the next loop
*/
if ((clk_status_reg_val & clk_status_bit) == 0) {
hif_func->hif_write_reg(wilc, wakeup_reg,
wakeup_reg_val & (~wakeup_bit));
/* added wait before wakeup sequence retry */
usleep_range(200, 300);
}
} while ((clk_status_reg_val & clk_status_bit) == 0 && wake_seq_trials-- > 0);
if (!wake_seq_trials)
dev_err(wilc->dev, "clocks still OFF. Wake up failed\n");
return 0;
}
static int chip_wakeup(struct wilc *wilc)
{
if (is_wilc1000(wilc->chipid))
return chip_wakeup_wilc1000(wilc);
else
return chip_wakeup_wilc3000(wilc);
}
static inline int acquire_bus(struct wilc *wilc, enum bus_acquire acquire)
{
int ret = 0;
mutex_lock(&wilc->hif_cs);
if (acquire == WILC_BUS_ACQUIRE_AND_WAKEUP && wilc->power_save_mode) {
ret = chip_wakeup(wilc);
if (ret)
mutex_unlock(&wilc->hif_cs);
}
return ret;
}
static inline int release_bus(struct wilc *wilc, enum bus_release release)
{
int ret = 0;
if (release == WILC_BUS_RELEASE_ALLOW_SLEEP && wilc->power_save_mode)
ret = chip_allow_sleep(wilc);
mutex_unlock(&wilc->hif_cs);
return ret;
}
int host_wakeup_notify(struct wilc *wilc)
{
int ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
return ret;
wilc->hif_func->hif_write_reg(wilc, is_wilc1000(wilc->chipid) ?
WILC1000_CORTUS_INTERRUPT_2 :
WILC3000_CORTUS_INTERRUPT_2, 1);
return release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
}
staging: wilc1000: revert "fix TODO to compile spi and sdio components in single module" The TODO item named "make spi and sdio components coexist in one build" was apparently addressed a long time ago, but never removed from the TODO file. However, the new patch that tries to address it actually makes it worse again by duplicating the common parts of the driver into two separate modules rather than sharing them. This also introduces a build regression when one of the two is built-in while the other is a loadable module: drivers/staging/wilc1000/wilc_debugfs.o:(.data+0x10): undefined reference to `__this_module' Reverting the patch makes it build again. I'm leaving the TODO file modification though, as there is nothing left to do for this item. A related problem however still seems to exist: one still cannot have multiple concurrent instances of wilc1000 devices present in the system, as there are lots of shared global variables such as host_interface.c:static struct wilc_vif *periodic_rssi_vif; wilc_sdio.c:static struct wilc_sdio g_sdio; wilc_wlan.c:static enum chip_ps_states chip_ps_state = CHIP_WAKEDUP; wilc_wlan.c:static u32 pending_acks; wilc_wfi_cfgoperations.c:int wilc_connecting; In order to have multiple instances working (sdio, spi, or mixed), all such variables need to be dynamically allocated per instance and stored in 'struct wilc' or one of the structures referenced by it. Fixes: 9abc44ba4e2f ("staging: wilc1000: fix TODO to compile spi and sdio components in single module") Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-08-13 23:20:33 +02:00
EXPORT_SYMBOL_GPL(host_wakeup_notify);
int host_sleep_notify(struct wilc *wilc)
{
int ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
return ret;
wilc->hif_func->hif_write_reg(wilc, is_wilc1000(wilc->chipid) ?
WILC1000_CORTUS_INTERRUPT_1 :
WILC3000_CORTUS_INTERRUPT_1, 1);
return release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
}
staging: wilc1000: revert "fix TODO to compile spi and sdio components in single module" The TODO item named "make spi and sdio components coexist in one build" was apparently addressed a long time ago, but never removed from the TODO file. However, the new patch that tries to address it actually makes it worse again by duplicating the common parts of the driver into two separate modules rather than sharing them. This also introduces a build regression when one of the two is built-in while the other is a loadable module: drivers/staging/wilc1000/wilc_debugfs.o:(.data+0x10): undefined reference to `__this_module' Reverting the patch makes it build again. I'm leaving the TODO file modification though, as there is nothing left to do for this item. A related problem however still seems to exist: one still cannot have multiple concurrent instances of wilc1000 devices present in the system, as there are lots of shared global variables such as host_interface.c:static struct wilc_vif *periodic_rssi_vif; wilc_sdio.c:static struct wilc_sdio g_sdio; wilc_wlan.c:static enum chip_ps_states chip_ps_state = CHIP_WAKEDUP; wilc_wlan.c:static u32 pending_acks; wilc_wfi_cfgoperations.c:int wilc_connecting; In order to have multiple instances working (sdio, spi, or mixed), all such variables need to be dynamically allocated per instance and stored in 'struct wilc' or one of the structures referenced by it. Fixes: 9abc44ba4e2f ("staging: wilc1000: fix TODO to compile spi and sdio components in single module") Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-08-13 23:20:33 +02:00
EXPORT_SYMBOL_GPL(host_sleep_notify);
int wilc_wlan_handle_txq(struct wilc *wilc, u32 *txq_count)
{
int i, entries = 0;
u8 k, ac;
u32 sum;
u32 reg;
u8 ac_desired_ratio[NQUEUES] = {0, 0, 0, 0};
u8 ac_preserve_ratio[NQUEUES] = {1, 1, 1, 1};
u8 *num_pkts_to_add;
u8 vmm_entries_ac[WILC_VMM_TBL_SIZE];
u32 offset = 0;
bool max_size_over = 0, ac_exist = 0;
int vmm_sz = 0;
struct txq_entry_t *tqe_q[NQUEUES];
int ret = 0;
int counter;
int timeout;
u32 *vmm_table = wilc->vmm_table;
u8 ac_pkt_num_to_chip[NQUEUES] = {0, 0, 0, 0};
const struct wilc_hif_func *func;
Revert "wifi: wilc1000: convert list management to RCU" This reverts commit f236464f1db7bea80075e6e31ac70dc6eb80547f Commit f236464f1db7 ("wifi: wilc1000: convert list management to RCU") replaced SRCU with RCU, aiming to simplify RCU usage in the driver. No documentation or commit history hinted about why SRCU has been preferred in original design, so it has been assumed to be safe to do this conversion. Unfortunately, some static analyzers raised warnings, confirmed by runtime checker, not long after the merge. At least three different issues arose when switching to RCU: - wilc_wlan_txq_filter_dup_tcp_ack is executed in a RCU read critical section yet calls wait_for_completion_timeout - wilc_wfi_init_mon_interface calls kmalloc and register_netdevice while manipulating a vif retrieved from vif list - set_channel sends command to chip (and so, also waits for a completion) while holding a vif retrieved from vif list (so, in RCU read critical section) Some of those issues are not trivial to fix and would need bigger driver rework. Fix those issues by reverting the SRCU to RCU conversion commit Reported-by: Dan Carpenter <dan.carpenter@linaro.org> Closes: https://lore.kernel.org/linux-wireless/3b46ec7c-baee-49fd-b760-3bc12fb12eaf@moroto.mountain/ Fixes: f236464f1db7 ("wifi: wilc1000: convert list management to RCU") Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com> Signed-off-by: Kalle Valo <kvalo@kernel.org> Link: https://msgid.link/20240528-wilc_revert_srcu_to_rcu-v1-1-bce096e0798c@bootlin.com
2024-05-28 16:20:28 +02:00
int srcu_idx;
u8 *txb = wilc->tx_buffer;
struct wilc_vif *vif;
int rv;
if (wilc->quit)
goto out_update_cnt;
if (ac_balance(wilc, ac_desired_ratio))
return -EINVAL;
mutex_lock(&wilc->txq_add_to_head_cs);
Revert "wifi: wilc1000: convert list management to RCU" This reverts commit f236464f1db7bea80075e6e31ac70dc6eb80547f Commit f236464f1db7 ("wifi: wilc1000: convert list management to RCU") replaced SRCU with RCU, aiming to simplify RCU usage in the driver. No documentation or commit history hinted about why SRCU has been preferred in original design, so it has been assumed to be safe to do this conversion. Unfortunately, some static analyzers raised warnings, confirmed by runtime checker, not long after the merge. At least three different issues arose when switching to RCU: - wilc_wlan_txq_filter_dup_tcp_ack is executed in a RCU read critical section yet calls wait_for_completion_timeout - wilc_wfi_init_mon_interface calls kmalloc and register_netdevice while manipulating a vif retrieved from vif list - set_channel sends command to chip (and so, also waits for a completion) while holding a vif retrieved from vif list (so, in RCU read critical section) Some of those issues are not trivial to fix and would need bigger driver rework. Fix those issues by reverting the SRCU to RCU conversion commit Reported-by: Dan Carpenter <dan.carpenter@linaro.org> Closes: https://lore.kernel.org/linux-wireless/3b46ec7c-baee-49fd-b760-3bc12fb12eaf@moroto.mountain/ Fixes: f236464f1db7 ("wifi: wilc1000: convert list management to RCU") Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com> Signed-off-by: Kalle Valo <kvalo@kernel.org> Link: https://msgid.link/20240528-wilc_revert_srcu_to_rcu-v1-1-bce096e0798c@bootlin.com
2024-05-28 16:20:28 +02:00
srcu_idx = srcu_read_lock(&wilc->srcu);
wifi: wilc1000: use SRCU instead of RCU for vif list traversal Enabling CONFIG_PROVE_RCU_LIST raises many warnings in wilc driver, even on some places already protected by a read critical section. An example of such case is in wilc_get_available_idx: ============================= WARNING: suspicious RCU usage 6.8.0-rc1+ #32 Not tainted ----------------------------- drivers/net/wireless/microchip/wilc1000/netdev.c:944 RCU-list traversed in non-reader section!! [...] stack backtrace: CPU: 0 PID: 26 Comm: kworker/0:3 Not tainted 6.8.0-rc1+ #32 Hardware name: Atmel SAMA5 Workqueue: events_freezable mmc_rescan unwind_backtrace from show_stack+0x18/0x1c show_stack from dump_stack_lvl+0x34/0x58 dump_stack_lvl from wilc_netdev_ifc_init+0x788/0x8ec wilc_netdev_ifc_init from wilc_cfg80211_init+0x690/0x910 wilc_cfg80211_init from wilc_sdio_probe+0x168/0x490 wilc_sdio_probe from sdio_bus_probe+0x230/0x3f4 sdio_bus_probe from really_probe+0x270/0xdf4 really_probe from __driver_probe_device+0x1dc/0x580 __driver_probe_device from driver_probe_device+0x60/0x140 driver_probe_device from __device_attach_driver+0x268/0x364 __device_attach_driver from bus_for_each_drv+0x15c/0x1cc bus_for_each_drv from __device_attach+0x1ec/0x3e8 __device_attach from bus_probe_device+0x190/0x1c0 bus_probe_device from device_add+0x10dc/0x18e4 device_add from sdio_add_func+0x1c0/0x2c0 sdio_add_func from mmc_attach_sdio+0xa08/0xe1c mmc_attach_sdio from mmc_rescan+0xa00/0xfe0 mmc_rescan from process_one_work+0x8d4/0x169c process_one_work from worker_thread+0x8cc/0x1340 worker_thread from kthread+0x448/0x510 kthread from ret_from_fork+0x14/0x28 This warning is due to the section being protected by a srcu critical read section, but the list traversal being done with classic RCU API. Fix the warning by using corresponding SRCU read lock/unlock APIs. While doing so, since we always manipulate the same list (managed through a pointer embedded in struct_wilc), add a macro to reduce the corresponding boilerplate in each call site. Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com> Signed-off-by: Kalle Valo <kvalo@kernel.org> Link: https://msgid.link/20240215-wilc_fix_rcu_usage-v1-2-f610e46c6f82@bootlin.com
2024-02-15 16:36:19 +01:00
wilc_for_each_vif(wilc, vif)
wilc_wlan_txq_filter_dup_tcp_ack(vif->ndev);
Revert "wifi: wilc1000: convert list management to RCU" This reverts commit f236464f1db7bea80075e6e31ac70dc6eb80547f Commit f236464f1db7 ("wifi: wilc1000: convert list management to RCU") replaced SRCU with RCU, aiming to simplify RCU usage in the driver. No documentation or commit history hinted about why SRCU has been preferred in original design, so it has been assumed to be safe to do this conversion. Unfortunately, some static analyzers raised warnings, confirmed by runtime checker, not long after the merge. At least three different issues arose when switching to RCU: - wilc_wlan_txq_filter_dup_tcp_ack is executed in a RCU read critical section yet calls wait_for_completion_timeout - wilc_wfi_init_mon_interface calls kmalloc and register_netdevice while manipulating a vif retrieved from vif list - set_channel sends command to chip (and so, also waits for a completion) while holding a vif retrieved from vif list (so, in RCU read critical section) Some of those issues are not trivial to fix and would need bigger driver rework. Fix those issues by reverting the SRCU to RCU conversion commit Reported-by: Dan Carpenter <dan.carpenter@linaro.org> Closes: https://lore.kernel.org/linux-wireless/3b46ec7c-baee-49fd-b760-3bc12fb12eaf@moroto.mountain/ Fixes: f236464f1db7 ("wifi: wilc1000: convert list management to RCU") Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com> Signed-off-by: Kalle Valo <kvalo@kernel.org> Link: https://msgid.link/20240528-wilc_revert_srcu_to_rcu-v1-1-bce096e0798c@bootlin.com
2024-05-28 16:20:28 +02:00
srcu_read_unlock(&wilc->srcu, srcu_idx);
for (ac = 0; ac < NQUEUES; ac++)
tqe_q[ac] = wilc_wlan_txq_get_first(wilc, ac);
i = 0;
sum = 0;
max_size_over = 0;
num_pkts_to_add = ac_desired_ratio;
do {
ac_exist = 0;
for (ac = 0; (ac < NQUEUES) && (!max_size_over); ac++) {
if (!tqe_q[ac])
continue;
ac_exist = 1;
for (k = 0; (k < num_pkts_to_add[ac]) &&
(!max_size_over) && tqe_q[ac]; k++) {
if (i >= (WILC_VMM_TBL_SIZE - 1)) {
max_size_over = 1;
break;
}
if (tqe_q[ac]->type == WILC_CFG_PKT)
vmm_sz = ETH_CONFIG_PKT_HDR_OFFSET;
else if (tqe_q[ac]->type == WILC_NET_PKT)
vmm_sz = ETH_ETHERNET_HDR_OFFSET;
else
vmm_sz = HOST_HDR_OFFSET;
vmm_sz += tqe_q[ac]->buffer_size;
vmm_sz = ALIGN(vmm_sz, 4);
if ((sum + vmm_sz) > WILC_TX_BUFF_SIZE) {
max_size_over = 1;
break;
}
vmm_table[i] = vmm_sz / 4;
if (tqe_q[ac]->type == WILC_CFG_PKT)
vmm_table[i] |= BIT(10);
cpu_to_le32s(&vmm_table[i]);
vmm_entries_ac[i] = ac;
i++;
sum += vmm_sz;
tqe_q[ac] = wilc_wlan_txq_get_next(wilc,
tqe_q[ac],
ac);
}
}
num_pkts_to_add = ac_preserve_ratio;
} while (!max_size_over && ac_exist);
if (i == 0)
goto out_unlock;
vmm_table[i] = 0x0;
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
goto out_unlock;
counter = 0;
func = wilc->hif_func;
do {
ret = func->hif_read_reg(wilc, WILC_HOST_TX_CTRL, &reg);
if (ret)
break;
if ((reg & 0x1) == 0) {
ac_update_fw_ac_pkt_info(wilc, reg);
break;
}
counter++;
if (counter > 200) {
counter = 0;
ret = func->hif_write_reg(wilc, WILC_HOST_TX_CTRL, 0);
break;
}
} while (!wilc->quit);
if (ret)
goto out_release_bus;
timeout = 200;
do {
ret = func->hif_block_tx(wilc,
WILC_VMM_TBL_RX_SHADOW_BASE,
(u8 *)vmm_table,
((i + 1) * 4));
if (ret)
break;
if (is_wilc1000(wilc->chipid)) {
ret = func->hif_write_reg(wilc, WILC_HOST_VMM_CTL, 0x2);
if (ret)
break;
do {
ret = func->hif_read_reg(wilc, WILC_HOST_VMM_CTL, &reg);
if (ret)
break;
if (FIELD_GET(WILC_VMM_ENTRY_AVAILABLE, reg)) {
entries = FIELD_GET(WILC_VMM_ENTRY_COUNT, reg);
break;
}
} while (--timeout);
} else {
ret = func->hif_write_reg(wilc, WILC_HOST_VMM_CTL, 0);
if (ret)
break;
/* interrupt firmware */
ret = func->hif_write_reg(wilc, WILC_CORTUS_INTERRUPT_BASE, 1);
if (ret)
break;
do {
ret = func->hif_read_reg(wilc, WILC_CORTUS_INTERRUPT_BASE, &reg);
if (ret)
break;
if (reg == 0) {
/* Get the entries */
ret = func->hif_read_reg(wilc, WILC_HOST_VMM_CTL, &reg);
if (ret)
break;
entries = FIELD_GET(WILC_VMM_ENTRY_COUNT, reg);
break;
}
} while (--timeout);
}
if (timeout <= 0) {
ret = func->hif_write_reg(wilc, WILC_HOST_VMM_CTL, 0x0);
break;
}
if (ret)
break;
if (entries == 0) {
ret = func->hif_read_reg(wilc, WILC_HOST_TX_CTRL, &reg);
if (ret)
break;
reg &= ~BIT(0);
ret = func->hif_write_reg(wilc, WILC_HOST_TX_CTRL, reg);
}
} while (0);
if (ret)
goto out_release_bus;
if (entries == 0) {
/*
* No VMM space available in firmware so retry to transmit
* the packet from tx queue.
*/
ret = WILC_VMM_ENTRY_FULL_RETRY;
goto out_release_bus;
}
ret = release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
if (ret)
goto out_unlock;
offset = 0;
i = 0;
do {
struct txq_entry_t *tqe;
u32 header, buffer_offset;
char *bssid;
u8 mgmt_ptk = 0;
if (vmm_table[i] == 0 || vmm_entries_ac[i] >= NQUEUES)
break;
tqe = wilc_wlan_txq_remove_from_head(wilc, vmm_entries_ac[i]);
if (!tqe)
break;
ac_pkt_num_to_chip[vmm_entries_ac[i]]++;
vif = tqe->vif;
le32_to_cpus(&vmm_table[i]);
vmm_sz = FIELD_GET(WILC_VMM_BUFFER_SIZE, vmm_table[i]);
vmm_sz *= 4;
if (tqe->type == WILC_MGMT_PKT)
mgmt_ptk = 1;
header = (FIELD_PREP(WILC_VMM_HDR_TYPE, tqe->type) |
FIELD_PREP(WILC_VMM_HDR_MGMT_FIELD, mgmt_ptk) |
FIELD_PREP(WILC_VMM_HDR_PKT_SIZE, tqe->buffer_size) |
FIELD_PREP(WILC_VMM_HDR_BUFF_SIZE, vmm_sz));
cpu_to_le32s(&header);
memcpy(&txb[offset], &header, 4);
if (tqe->type == WILC_CFG_PKT) {
buffer_offset = ETH_CONFIG_PKT_HDR_OFFSET;
} else if (tqe->type == WILC_NET_PKT) {
int prio = tqe->q_num;
bssid = tqe->vif->bssid;
buffer_offset = ETH_ETHERNET_HDR_OFFSET;
memcpy(&txb[offset + 4], &prio, sizeof(prio));
memcpy(&txb[offset + 8], bssid, 6);
} else {
buffer_offset = HOST_HDR_OFFSET;
}
memcpy(&txb[offset + buffer_offset],
tqe->buffer, tqe->buffer_size);
offset += vmm_sz;
i++;
tqe->status = 1;
if (tqe->tx_complete_func)
tqe->tx_complete_func(tqe->priv, tqe->status);
if (tqe->ack_idx != NOT_TCP_ACK &&
tqe->ack_idx < MAX_PENDING_ACKS)
vif->ack_filter.pending_acks[tqe->ack_idx].txqe = NULL;
kfree(tqe);
} while (--entries);
for (i = 0; i < NQUEUES; i++)
wilc->txq[i].fw.count += ac_pkt_num_to_chip[i];
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
goto out_unlock;
ret = func->hif_clear_int_ext(wilc, ENABLE_TX_VMM);
if (ret)
goto out_release_bus;
ret = func->hif_block_tx_ext(wilc, 0, txb, offset);
out_release_bus:
rv = release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
if (!ret && rv)
ret = rv;
out_unlock:
mutex_unlock(&wilc->txq_add_to_head_cs);
out_update_cnt:
*txq_count = wilc->txq_entries;
return ret;
}
static void wilc_wlan_handle_rx_buff(struct wilc *wilc, u8 *buffer, int size)
{
int offset = 0;
u32 header;
u32 pkt_len, pkt_offset, tp_len;
int is_cfg_packet;
u8 *buff_ptr;
do {
buff_ptr = buffer + offset;
header = get_unaligned_le32(buff_ptr);
is_cfg_packet = FIELD_GET(WILC_PKT_HDR_CONFIG_FIELD, header);
pkt_offset = FIELD_GET(WILC_PKT_HDR_OFFSET_FIELD, header);
tp_len = FIELD_GET(WILC_PKT_HDR_TOTAL_LEN_FIELD, header);
pkt_len = FIELD_GET(WILC_PKT_HDR_LEN_FIELD, header);
if (pkt_len == 0 || tp_len == 0)
break;
if (pkt_offset & IS_MANAGMEMENT) {
buff_ptr += HOST_HDR_OFFSET;
wilc_wfi_mgmt_rx(wilc, buff_ptr, pkt_len,
pkt_offset & IS_MGMT_AUTH_PKT);
} else {
if (!is_cfg_packet) {
wilc_frmw_to_host(wilc, buff_ptr, pkt_len,
pkt_offset);
} else {
struct wilc_cfg_rsp rsp;
buff_ptr += pkt_offset;
wilc_wlan_cfg_indicate_rx(wilc, buff_ptr,
pkt_len,
&rsp);
if (rsp.type == WILC_CFG_RSP) {
if (wilc->cfg_seq_no == rsp.seq_no)
complete(&wilc->cfg_event);
} else if (rsp.type == WILC_CFG_RSP_STATUS) {
wilc_mac_indicate(wilc);
}
}
}
offset += tp_len;
} while (offset < size);
}
static void wilc_wlan_handle_rxq(struct wilc *wilc)
{
int size;
u8 *buffer;
struct rxq_entry_t *rqe;
while (!wilc->quit) {
rqe = wilc_wlan_rxq_remove(wilc);
if (!rqe)
break;
buffer = rqe->buffer;
size = rqe->buffer_size;
wilc_wlan_handle_rx_buff(wilc, buffer, size);
kfree(rqe);
}
if (wilc->quit)
complete(&wilc->cfg_event);
}
static void wilc_unknown_isr_ext(struct wilc *wilc)
{
wilc->hif_func->hif_clear_int_ext(wilc, 0);
}
static void wilc_wlan_handle_isr_ext(struct wilc *wilc, u32 int_status)
{
u32 offset = wilc->rx_buffer_offset;
u8 *buffer = NULL;
u32 size;
u32 retries = 0;
int ret = 0;
struct rxq_entry_t *rqe;
size = FIELD_GET(WILC_INTERRUPT_DATA_SIZE, int_status) << 2;
while (!size && retries < 10) {
wilc->hif_func->hif_read_size(wilc, &size);
size = FIELD_GET(WILC_INTERRUPT_DATA_SIZE, size) << 2;
retries++;
}
if (size <= 0)
return;
if (WILC_RX_BUFF_SIZE - offset < size)
offset = 0;
buffer = &wilc->rx_buffer[offset];
wilc->hif_func->hif_clear_int_ext(wilc, DATA_INT_CLR | ENABLE_RX_VMM);
ret = wilc->hif_func->hif_block_rx_ext(wilc, 0, buffer, size);
if (ret)
return;
offset += size;
wilc->rx_buffer_offset = offset;
rqe = kmalloc(sizeof(*rqe), GFP_KERNEL);
if (!rqe)
return;
rqe->buffer = buffer;
rqe->buffer_size = size;
wilc_wlan_rxq_add(wilc, rqe);
wilc_wlan_handle_rxq(wilc);
}
void wilc_handle_isr(struct wilc *wilc)
{
u32 int_status;
int ret;
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret) {
dev_err_ratelimited(wilc->dev, "Cannot acquire bus\n");
return;
}
wilc->hif_func->hif_read_int(wilc, &int_status);
if (int_status & DATA_INT_EXT)
wilc_wlan_handle_isr_ext(wilc, int_status);
if (!(int_status & (ALL_INT_EXT)))
wilc_unknown_isr_ext(wilc);
ret = release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
if (ret)
dev_err_ratelimited(wilc->dev, "Cannot release bus\n");
}
staging: wilc1000: revert "fix TODO to compile spi and sdio components in single module" The TODO item named "make spi and sdio components coexist in one build" was apparently addressed a long time ago, but never removed from the TODO file. However, the new patch that tries to address it actually makes it worse again by duplicating the common parts of the driver into two separate modules rather than sharing them. This also introduces a build regression when one of the two is built-in while the other is a loadable module: drivers/staging/wilc1000/wilc_debugfs.o:(.data+0x10): undefined reference to `__this_module' Reverting the patch makes it build again. I'm leaving the TODO file modification though, as there is nothing left to do for this item. A related problem however still seems to exist: one still cannot have multiple concurrent instances of wilc1000 devices present in the system, as there are lots of shared global variables such as host_interface.c:static struct wilc_vif *periodic_rssi_vif; wilc_sdio.c:static struct wilc_sdio g_sdio; wilc_wlan.c:static enum chip_ps_states chip_ps_state = CHIP_WAKEDUP; wilc_wlan.c:static u32 pending_acks; wilc_wfi_cfgoperations.c:int wilc_connecting; In order to have multiple instances working (sdio, spi, or mixed), all such variables need to be dynamically allocated per instance and stored in 'struct wilc' or one of the structures referenced by it. Fixes: 9abc44ba4e2f ("staging: wilc1000: fix TODO to compile spi and sdio components in single module") Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-08-13 23:20:33 +02:00
EXPORT_SYMBOL_GPL(wilc_handle_isr);
int wilc_wlan_firmware_download(struct wilc *wilc, const u8 *buffer,
u32 buffer_size)
{
u32 offset;
u32 addr, size, size2, blksz;
u8 *dma_buffer;
int ret = 0;
u32 reg = 0;
int rv;
blksz = BIT(12);
dma_buffer = kmalloc(blksz, GFP_KERNEL);
if (!dma_buffer)
return -EIO;
offset = 0;
pr_debug("%s: Downloading firmware size = %d\n", __func__, buffer_size);
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
return ret;
wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, &reg);
reg &= ~BIT(10);
ret = wilc->hif_func->hif_write_reg(wilc, WILC_GLB_RESET_0, reg);
wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, &reg);
if (reg & BIT(10))
pr_err("%s: Failed to reset\n", __func__);
ret = release_bus(wilc, WILC_BUS_RELEASE_ONLY);
if (ret)
goto fail;
do {
addr = get_unaligned_le32(&buffer[offset]);
size = get_unaligned_le32(&buffer[offset + 4]);
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
goto fail;
offset += 8;
while (((int)size) && (offset < buffer_size)) {
size2 = min(size, blksz);
memcpy(dma_buffer, &buffer[offset], size2);
ret = wilc->hif_func->hif_block_tx(wilc, addr,
dma_buffer, size2);
if (ret)
break;
addr += size2;
offset += size2;
size -= size2;
}
rv = release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
if (!ret && rv)
ret = rv;
if (ret) {
pr_err("%s Bus error\n", __func__);
goto fail;
}
pr_debug("%s Offset = %d\n", __func__, offset);
} while (offset < buffer_size);
fail:
kfree(dma_buffer);
return ret;
}
int wilc_wlan_start(struct wilc *wilc)
{
u32 reg = 0;
int ret, rv;
u32 chipid;
if (wilc->io_type == WILC_HIF_SDIO) {
reg = 0;
reg |= BIT(3);
} else if (wilc->io_type == WILC_HIF_SPI) {
reg = 1;
}
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_ONLY);
if (ret)
return ret;
ret = wilc->hif_func->hif_write_reg(wilc, WILC_VMM_CORE_CFG, reg);
if (ret)
goto release;
reg = 0;
if (wilc->io_type == WILC_HIF_SDIO && wilc->dev_irq_num)
reg |= WILC_HAVE_SDIO_IRQ_GPIO;
if (is_wilc3000(wilc->chipid))
reg |= WILC_HAVE_SLEEP_CLK_SRC_RTC;
ret = wilc->hif_func->hif_write_reg(wilc, WILC_GP_REG_1, reg);
if (ret)
goto release;
wilc->hif_func->hif_sync_ext(wilc, NUM_INT_EXT);
ret = wilc->hif_func->hif_read_reg(wilc, WILC_CHIPID, &chipid);
if (ret)
goto release;
wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, &reg);
if ((reg & BIT(10)) == BIT(10)) {
reg &= ~BIT(10);
wilc->hif_func->hif_write_reg(wilc, WILC_GLB_RESET_0, reg);
wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, &reg);
}
reg |= BIT(10);
ret = wilc->hif_func->hif_write_reg(wilc, WILC_GLB_RESET_0, reg);
wilc->hif_func->hif_read_reg(wilc, WILC_GLB_RESET_0, &reg);
release:
rv = release_bus(wilc, WILC_BUS_RELEASE_ONLY);
return ret ? ret : rv;
}
int wilc_wlan_stop(struct wilc *wilc, struct wilc_vif *vif)
{
u32 reg = 0;
int ret, rv;
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
return ret;
ret = wilc->hif_func->hif_read_reg(wilc, GLOBAL_MODE_CONTROL, &reg);
if (ret)
goto release;
reg &= ~WILC_GLOBAL_MODE_ENABLE_WIFI;
ret = wilc->hif_func->hif_write_reg(wilc, GLOBAL_MODE_CONTROL, reg);
if (ret)
goto release;
ret = wilc->hif_func->hif_read_reg(wilc, PWR_SEQ_MISC_CTRL, &reg);
if (ret)
goto release;
reg &= ~WILC_PWR_SEQ_ENABLE_WIFI_SLEEP;
ret = wilc->hif_func->hif_write_reg(wilc, PWR_SEQ_MISC_CTRL, reg);
if (ret)
goto release;
ret = wilc->hif_func->hif_read_reg(wilc, WILC_GP_REG_0, &reg);
if (ret) {
netdev_err(vif->ndev, "Error while reading reg\n");
goto release;
}
ret = wilc->hif_func->hif_write_reg(wilc, WILC_GP_REG_0,
(reg | WILC_ABORT_REQ_BIT));
if (ret) {
netdev_err(vif->ndev, "Error while writing reg\n");
goto release;
}
ret = 0;
release:
/* host comm is disabled - we can't issue sleep command anymore: */
rv = release_bus(wilc, WILC_BUS_RELEASE_ONLY);
return ret ? ret : rv;
}
void wilc_wlan_cleanup(struct net_device *dev)
{
struct txq_entry_t *tqe;
struct rxq_entry_t *rqe;
u8 ac;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc = vif->wilc;
wilc->quit = 1;
for (ac = 0; ac < NQUEUES; ac++) {
while ((tqe = wilc_wlan_txq_remove_from_head(wilc, ac))) {
if (tqe->tx_complete_func)
tqe->tx_complete_func(tqe->priv, 0);
kfree(tqe);
}
}
while ((rqe = wilc_wlan_rxq_remove(wilc)))
kfree(rqe);
kfree(wilc->vmm_table);
wilc->vmm_table = NULL;
kfree(wilc->rx_buffer);
wilc->rx_buffer = NULL;
kfree(wilc->tx_buffer);
wilc->tx_buffer = NULL;
wilc->hif_func->hif_deinit(wilc);
}
static int wilc_wlan_cfg_commit(struct wilc_vif *vif, int type,
u32 drv_handler)
{
struct wilc *wilc = vif->wilc;
struct wilc_cfg_frame *cfg = &wilc->cfg_frame;
int t_len = wilc->cfg_frame_offset + sizeof(struct wilc_cfg_cmd_hdr);
if (type == WILC_CFG_SET)
cfg->hdr.cmd_type = 'W';
else
cfg->hdr.cmd_type = 'Q';
cfg->hdr.seq_no = wilc->cfg_seq_no % 256;
cfg->hdr.total_len = cpu_to_le16(t_len);
cfg->hdr.driver_handler = cpu_to_le32(drv_handler);
wilc->cfg_seq_no = cfg->hdr.seq_no;
if (!wilc_wlan_txq_add_cfg_pkt(vif, (u8 *)&cfg->hdr, t_len))
return -1;
return 0;
}
int wilc_wlan_cfg_set(struct wilc_vif *vif, int start, u16 wid, u8 *buffer,
u32 buffer_size, int commit, u32 drv_handler)
{
u32 offset;
int ret_size;
struct wilc *wilc = vif->wilc;
mutex_lock(&wilc->cfg_cmd_lock);
if (start)
wilc->cfg_frame_offset = 0;
offset = wilc->cfg_frame_offset;
ret_size = wilc_wlan_cfg_set_wid(wilc->cfg_frame.frame, offset,
wid, buffer, buffer_size);
offset += ret_size;
wilc->cfg_frame_offset = offset;
if (!commit) {
mutex_unlock(&wilc->cfg_cmd_lock);
return ret_size;
}
netdev_dbg(vif->ndev, "%s: seqno[%d]\n", __func__, wilc->cfg_seq_no);
if (wilc_wlan_cfg_commit(vif, WILC_CFG_SET, drv_handler))
ret_size = 0;
if (!wait_for_completion_timeout(&wilc->cfg_event,
WILC_CFG_PKTS_TIMEOUT)) {
netdev_dbg(vif->ndev, "%s: Timed Out\n", __func__);
ret_size = 0;
}
wilc->cfg_frame_offset = 0;
wilc->cfg_seq_no += 1;
mutex_unlock(&wilc->cfg_cmd_lock);
return ret_size;
}
int wilc_wlan_cfg_get(struct wilc_vif *vif, int start, u16 wid, int commit,
u32 drv_handler)
{
u32 offset;
int ret_size;
struct wilc *wilc = vif->wilc;
mutex_lock(&wilc->cfg_cmd_lock);
if (start)
wilc->cfg_frame_offset = 0;
offset = wilc->cfg_frame_offset;
ret_size = wilc_wlan_cfg_get_wid(wilc->cfg_frame.frame, offset, wid);
offset += ret_size;
wilc->cfg_frame_offset = offset;
if (!commit) {
mutex_unlock(&wilc->cfg_cmd_lock);
return ret_size;
}
if (wilc_wlan_cfg_commit(vif, WILC_CFG_QUERY, drv_handler))
ret_size = 0;
if (!wait_for_completion_timeout(&wilc->cfg_event,
WILC_CFG_PKTS_TIMEOUT)) {
netdev_dbg(vif->ndev, "%s: Timed Out\n", __func__);
ret_size = 0;
}
wilc->cfg_frame_offset = 0;
wilc->cfg_seq_no += 1;
mutex_unlock(&wilc->cfg_cmd_lock);
return ret_size;
}
int wilc_send_config_pkt(struct wilc_vif *vif, u8 mode, struct wid *wids,
u32 count)
{
int i;
int ret = 0;
u32 drv = wilc_get_vif_idx(vif);
if (mode == WILC_GET_CFG) {
for (i = 0; i < count; i++) {
if (!wilc_wlan_cfg_get(vif, !i,
wids[i].id,
(i == count - 1),
drv)) {
ret = -ETIMEDOUT;
break;
}
}
for (i = 0; i < count; i++) {
wids[i].size = wilc_wlan_cfg_get_val(vif->wilc,
wids[i].id,
wids[i].val,
wids[i].size);
}
} else if (mode == WILC_SET_CFG) {
for (i = 0; i < count; i++) {
if (!wilc_wlan_cfg_set(vif, !i,
wids[i].id,
wids[i].val,
wids[i].size,
(i == count - 1),
drv)) {
ret = -ETIMEDOUT;
break;
}
}
}
return ret;
}
int wilc_get_chipid(struct wilc *wilc)
{
u32 chipid = 0;
u32 rfrevid = 0;
if (wilc->chipid == 0) {
wilc->hif_func->hif_read_reg(wilc, WILC3000_CHIP_ID, &chipid);
if (!is_wilc3000(chipid)) {
wilc->hif_func->hif_read_reg(wilc, WILC_CHIPID, &chipid);
wilc->hif_func->hif_read_reg(wilc, WILC_RF_REVISION_ID,
&rfrevid);
if (!is_wilc1000(chipid)) {
wilc->chipid = 0;
return -EINVAL;
}
if (chipid == WILC_1000_BASE_ID_2A) { /* 0x1002A0 */
if (rfrevid != 0x1)
chipid = WILC_1000_BASE_ID_2A_REV1;
} else if (chipid == WILC_1000_BASE_ID_2B) { /* 0x1002B0 */
if (rfrevid == 0x4)
chipid = WILC_1000_BASE_ID_2B_REV1;
else if (rfrevid != 0x3)
chipid = WILC_1000_BASE_ID_2B_REV2;
}
}
wilc->chipid = chipid;
}
return 0;
}
EXPORT_SYMBOL_GPL(wilc_get_chipid);
static int init_chip(struct net_device *dev)
{
u32 reg;
int ret, rv;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc = vif->wilc;
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
return ret;
ret = wilc_get_chipid(wilc);
if (ret)
goto release;
if ((wilc->chipid & 0xfff) != 0xa0) {
ret = wilc->hif_func->hif_read_reg(wilc,
WILC_CORTUS_RESET_MUX_SEL,
&reg);
if (ret) {
netdev_err(dev, "fail read reg 0x1118\n");
goto release;
}
reg |= BIT(0);
ret = wilc->hif_func->hif_write_reg(wilc,
WILC_CORTUS_RESET_MUX_SEL,
reg);
if (ret) {
netdev_err(dev, "fail write reg 0x1118\n");
goto release;
}
ret = wilc->hif_func->hif_write_reg(wilc,
WILC_CORTUS_BOOT_REGISTER,
WILC_CORTUS_BOOT_FROM_IRAM);
if (ret) {
netdev_err(dev, "fail write reg 0xc0000\n");
goto release;
}
}
if (is_wilc3000(wilc->chipid)) {
ret = wilc->hif_func->hif_read_reg(wilc, WILC3000_BOOTROM_STATUS, &reg);
if (ret) {
netdev_err(dev, "failed to read WILC3000 BootROM status register\n");
goto release;
}
ret = wilc->hif_func->hif_write_reg(wilc, WILC3000_CORTUS_BOOT_REGISTER_2,
WILC_CORTUS_BOOT_FROM_IRAM);
if (ret) {
netdev_err(dev, "failed to write WILC3000 Boot register\n");
goto release;
}
}
release:
rv = release_bus(wilc, WILC_BUS_RELEASE_ALLOW_SLEEP);
return ret ? ret : rv;
}
int wilc_load_mac_from_nv(struct wilc *wl)
{
int ret, rv;
unsigned int i;
ret = acquire_bus(wl, WILC_BUS_ACQUIRE_AND_WAKEUP);
if (ret)
return ret;
for (i = 0; i < WILC_NVMEM_MAX_NUM_BANK; i++) {
int bank_offset = get_bank_offset_from_bank_index(i);
u32 reg1, reg2;
u8 invalid;
u8 used;
ret = wl->hif_func->hif_read_reg(wl,
WILC_NVMEM_BANK_BASE + bank_offset,
&reg1);
if (ret) {
pr_err("Can not read address %d lower part", i);
break;
}
ret = wl->hif_func->hif_read_reg(wl,
WILC_NVMEM_BANK_BASE + bank_offset + 4,
&reg2);
if (ret) {
pr_err("Can not read address %d upper part", i);
break;
}
used = FIELD_GET(WILC_NVMEM_IS_BANK_USED, reg1);
invalid = FIELD_GET(WILC_NVMEM_IS_BANK_INVALID, reg1);
if (!used || invalid)
continue;
wl->nv_mac_address[0] = FIELD_GET(GENMASK(23, 16), reg1);
wl->nv_mac_address[1] = FIELD_GET(GENMASK(15, 8), reg1);
wl->nv_mac_address[2] = FIELD_GET(GENMASK(7, 0), reg1);
wl->nv_mac_address[3] = FIELD_GET(GENMASK(31, 24), reg2);
wl->nv_mac_address[4] = FIELD_GET(GENMASK(23, 16), reg2);
wl->nv_mac_address[5] = FIELD_GET(GENMASK(15, 8), reg2);
ret = 0;
break;
}
rv = release_bus(wl, WILC_BUS_RELEASE_ALLOW_SLEEP);
return ret ? ret : rv;
}
EXPORT_SYMBOL_GPL(wilc_load_mac_from_nv);
int wilc_wlan_init(struct net_device *dev)
{
int ret = 0, rv;
struct wilc_vif *vif = netdev_priv(dev);
struct wilc *wilc;
wilc = vif->wilc;
wilc->quit = 0;
if (!wilc->hif_func->hif_is_init(wilc)) {
ret = acquire_bus(wilc, WILC_BUS_ACQUIRE_ONLY);
if (ret)
return ret;
ret = wilc->hif_func->hif_init(wilc, false);
if (!ret)
ret = wilc_get_chipid(wilc);
rv = release_bus(wilc, WILC_BUS_RELEASE_ONLY);
if (!ret && rv)
ret = rv;
if (ret)
goto fail;
if (!is_wilc1000(wilc->chipid) && !is_wilc3000(wilc->chipid)) {
netdev_err(dev, "Unsupported chipid: %x\n", wilc->chipid);
ret = -EINVAL;
goto fail;
}
netdev_dbg(dev, "chipid (%08x)\n", wilc->chipid);
}
if (!wilc->vmm_table)
wilc->vmm_table = kcalloc(WILC_VMM_TBL_SIZE, sizeof(u32), GFP_KERNEL);
if (!wilc->vmm_table) {
ret = -ENOBUFS;
goto fail;
}
if (!wilc->tx_buffer)
wilc->tx_buffer = kmalloc(WILC_TX_BUFF_SIZE, GFP_KERNEL);
if (!wilc->tx_buffer) {
ret = -ENOBUFS;
goto fail;
}
if (!wilc->rx_buffer)
wilc->rx_buffer = kmalloc(WILC_RX_BUFF_SIZE, GFP_KERNEL);
if (!wilc->rx_buffer) {
ret = -ENOBUFS;
goto fail;
}
if (init_chip(dev)) {
ret = -EIO;
goto fail;
}
return 0;
fail:
kfree(wilc->vmm_table);
wilc->vmm_table = NULL;
kfree(wilc->rx_buffer);
wilc->rx_buffer = NULL;
kfree(wilc->tx_buffer);
wilc->tx_buffer = NULL;
return ret;
}